Schellingの分居モデル

各エージェントは「近所の一定割合が自分と同じ種でないと不満」というだけのルールで動く。 閾値 0.30——近所の7割が異種でも構わない、という個人としてはきわめて穏当な選好——でも、 格子はほぼ完全に2色へ分離する。個人の微弱な選好と、集団に立ち現れる帰結は一致しない。 ページ下部の相図が、その乖離をそのまま示す。

種A 種B 空き 端は上下左右がつながったトーラス格子

平均同種率
不満者
スイープ0

相図

閾値を0.00から1.00まで0.02刻みで掃引し、各点で新規初期化・収束させたときの最終的な平均同種率をプロットする。 破線の45度線は「要求どおりの結果」。実線がそれを大きく上回る区間が、誰も望んでいない分離が起きている領域。 閾値が高すぎる右端では、誰も落ち着けず分離そのものが崩壊する。

未実行

アルゴリズムと実装

Thomas Schelling が1971年に提示したモデル。もとは硬貨をチェス盤に並べて手で動かす、それだけのものだった。

盤面

  • N×N の格子。各マスは 空き / 種A / 種B のいずれか。
  • 実体は長さ N² の Int8Array 一本。座標 (x, y) は y * N + x で引く。

ムーア近傍 — 「隣」をどう数えるか

エージェントが見るのは自分を囲む8マス。縦横だけでなく斜めも隣人として数える、 この数え方をムーア近傍という。

ムーア近傍(8マス)
このモデルで使うのはこちら
フォン・ノイマン近傍(4マス)
斜めを隣と見なさない流儀

斜めを含めるかどうかで結果は変わる。4マスだと満足の判定が「同種2マス以上」のように粗い刻みになり、 分離のかたちも角ばる。8マスのほうが刻みが細かく、境界もなめらかになる。

トーラス — 端のない盤面

右端のマスの右隣は、同じ行の左端。上端の上隣は下端。 つまり盤面がぐるりと一周してつながっている。紙を丸めて筒にし、その筒の両端もつなぐとドーナツ形になる。 この形をトーラスといい、そこから借りた呼び名。

橙が本人、灰が近傍。右端にいるので、近傍8マスのうち3マスは反対側の左端(青い破線)に回り込む。

こうするとどのマスも例外なく8つの隣を持ち、「端っこ」が存在しなくなる。 壁のある普通の盤面だと、角のマスは隣が3つしかない。 すると角の住人は「近所の過半数が同種」を達成しやすく、そこが人工的な核になって分離が始まってしまう。 見たいのは選好そのものが生む分離であって盤の形が生む分離ではないので、端を消しておく。

実装上は、隣を参照するときに座標を (x + 1) % N のように剰余で折り返すだけ。 このモデルではNごとに折り返し先を配列に事前計算し、内側ループから剰余演算を追い出している。

満足の判定

エージェントは近傍のうち 空きマスを除いた 分母で同種の割合を測り、 それが閾値以上なら満足、下回れば不満とする。

同種数 / (同種数 + 異種数) ≥ threshold

  • 空きマスを分母に入れないのが要点。入れてしまうと「空き率が高いほど不満」という、 モデルが表現したいものとは別の効果が混ざる。
  • 近傍が全て空きだと分母が0になる。この場合は満足として扱い(実装では同種率に -1 を返す)、 後述の平均からも除外する。

更新の単位 — スイープ

1フレームで1スイープ、つまりその時点の不満者を全員集め、順番をシャッフルして一巡ぶん処理する。 両極端はどちらも具合が悪い。

  • 1フレームに1人だけ動かす → 数千エージェントでは収束が見えるまで待てない。
  • 全員が同時に移動先を決めて一斉に動く → 同じ空きマスの奪い合いが起き、状態が振動して落ち着かない。

一巡の途中で周囲は変化するので、順番が回ってきた時点で改めて満足かどうかを測り直す。 リストに載った後で満足になったエージェントはその場に留まる。 逆に、自分より先に動いた誰かの移動先が自分のマスだった場合(=本人は既に移動済み)はスキップする。 シャッフルは処理順による偏りを消すため。

引っ越しを O(1) にする

不満なエージェントはランダムに選んだ空きマスへ移る。より良い場所を探すのではなく、 単に空いているところへ動くだけ、という点が重要(探索能力を仮定しないほうが、結果の意味が強くなる)。

素朴に書くと毎回グリッドを全走査して空きマスを探すことになり、移動1回あたり O(N²) かかる。 そこで空きマスのインデックスだけを別配列 emptyList に持つ。移動は

  • ランダムなスロット k を引き、移動先 dst = emptyList[k] を得る
  • グリッド上で src の値を dst へ移し、src を空きにする
  • emptyList[k] = src と上書きする

移動によって空きが1つ埋まり、同時に移動元が1つ空くので、空きマスの個数は最後まで変わらない。 だから配列は長さ固定のまま、1要素の書き換えだけで整合が保てる。移動は O(1)。 1スイープあたりの計算量は、全走査の O(N²) が支配する。

表示している統計

  • 平均同種率 — 全エージェントの近傍同種率の平均。ランダム配置の期待値は pA² + pB²(A:B が半々なら 0.5)で、そこからどれだけ上振れたかが分離の度合いを表す。 近傍が全て空きのエージェントは同種率が定義できないので除外している。
  • 不満者 — 現在の閾値で不満なエージェントの数と、全体に対する割合。
  • スイープ — 経過した更新回数。不満者が0になるか、上限3000で停止する。

閾値スライダーは実行中でも即座に反映される。動かすと統計だけが先に変わるので、 「同じ盤面でも、要求水準が変われば不満者の数がどう変わるか」をその場で見られる。

相図の作り方

  • 閾値を 0.00 から 1.00 まで 0.02 刻み、計51点。
  • 各点で盤面を新規初期化し、描画せずに収束させる(上限200スイープ)。
  • その時点の平均同種率を1点として打つ。
  • 51×200スイープはそれなりに重いので、格子は最大60×60に抑え、 3点ごとに await new Promise(r => setTimeout(r, 0)) で制御を返して画面が固まらないようにしている。

読み取るときの注意

  • 閾値0.5未満の領域が本体。 誰も多数派になりたいとは言っていないのに、 結果は同種率0.7〜0.8に達する。45度線からの乖離がそれ。
  • 右端で分離が崩壊する。 閾値が0.8を超えるあたりから、 どこへ移っても満足できないので誰も定着せず、盤面は撹拌され続けてランダム配置の水準に戻る。 上限スイープで打ち切った未収束の値なので、左側の点とは意味が違う。
  • これは差別の強さを測るモデルではない。 示しているのは、個人の選好と集団の帰結のあいだに単純な比例関係がない、ということ。 逆向きにも読める。分離した状態が観測されたからといって、 そこから強い選好を推定することはできない。