Модель сегрегации Шеллинга

Каждый агент согласен жить в смешанном окружении и хочет лишь, чтобы хотя бы 40% соседей были «своего» типа. Звучит как рецепт перемешанного общества. Модель Шеллинга показывает обратное: получается почти полное разделение.

Модель сегрегации Шеллинга — это клеточный автомат с агентами двух абстрактных типов на сетке, где каждый агент «доволен», если доля соседей своего типа не ниже заданного порога, а недовольные переезжают на случайную пустую клетку; модель демонстрирует, что даже умеренное локальное предпочтение приводит к сильной глобальной сегрегации.

Важно сразу оговорить: это абстрактная математическая модель «агентов двух типов», а не утверждение о людях, группах или обществе. Её ценность — методологическая: она показывает, что глобальный результат системы может радикально расходиться с намерениями отдельных её элементов. За работы в этом духе Томас Шеллинг получил Нобелевскую премию по экономике 2005 года. Мы изучаем её строго как пример эмерджентности.

Правила модели

На сетке живут агенты двух типов (обозначим их A и B) и есть пустые клетки. Окрестность — Мура (8 соседей). Для каждого агента:

  • Смотрим на занятых соседей и считаем долю «своих» — того же типа.
  • Агент доволен, если эта доля не ниже порога толерантности (в нашем примере thr = 0.4, то есть достаточно 40% своих).
  • Недовольный агент переезжает на случайную свободную клетку. Довольные остаются на месте.

Парадокс в том, что порог 0.4 описывает очень терпимого агента: он не против оказаться в меньшинстве (40% — это меньше половины). И всё же на уровне всей сетки возникает чёткое разделение. Посмотрим на коде.

import random
random.seed(2)
R, C = 8, 8
cells = [(r, c) for r in range(R) for c in range(C)]

def make_grid():
    g = [[0] * C for _ in range(R)]
    spots = cells[:]
    random.shuffle(spots)
    n_each = 24
    for i in range(n_each):
        r, c = spots[i]
        g[r][c] = 1
    for i in range(n_each, 2 * n_each):
        r, c = spots[i]
        g[r][c] = 2
    return g

g = make_grid()

def neighbors(g, r, c):
    same = diff = 0
    for dr in (-1, 0, 1):
        for dc in (-1, 0, 1):
            if dr == 0 and dc == 0:
                continue
            rr, cc = r + dr, c + dc
            if 0 <= rr < R and 0 <= cc < C and g[rr][cc] != 0:
                if g[rr][cc] == g[r][c]:
                    same += 1
                else:
                    diff += 1
    return same, diff

def happy(g, r, c, thr=0.4):
    if g[r][c] == 0:
        return True
    same, diff = neighbors(g, r, c)
    tot = same + diff
    if tot == 0:
        return True
    return same / tot >= thr

def frac_unhappy(g):
    tot = unh = 0
    for r in range(R):
        for c in range(C):
            if g[r][c] != 0:
                tot += 1
                if not happy(g, r, c):
                    unh += 1
    return unh / tot

def show(g):
    sym = {0: '.', 1: 'A', 2: 'B'}
    for row in g:
        print(''.join(sym[x] for x in row))
    print()

print("Старт, доля недовольных:", round(frac_unhappy(g), 3))
show(g)
for it in range(30):
    empties = [(r, c) for r in range(R) for c in range(C) if g[r][c] == 0]
    moved = False
    for r in range(R):
        for c in range(C):
            if g[r][c] != 0 and not happy(g, r, c) and empties:
                er, ec = random.choice(empties)
                g[er][ec] = g[r][c]
                g[r][c] = 0
                empties.remove((er, ec))
                empties.append((r, c))
                moved = True
    if not moved:
        break
print(f"После {it + 1} итераций, доля недовольных:", round(frac_unhappy(g), 3))
show(g)

Вывод:

Старт, доля недовольных: 0.25
AB.AA.A.
AA.AB.AA
.BA.BAA.
ABBBBBAA
BABABB.A
BB.BAAB.
BAB.B..A
.BBABB.A

После 4 итераций, доля недовольных: 0.0
AAAAA.AA
AA.AA.AA
AAA.BAAA
A.BBBBAA
B.B.BBAA
BB.B.BB.
B.BBBB..
.BB.BBB.

Сравните две сетки. В начале A и B перемешаны почти случайно, недовольных четверть. После всего 4 итераций недовольных не осталось вовсе, но посмотрите, какой ценой: A собрались в верхней части поля, B — в нижней. Возникли крупные одноцветные области. Никто не стремился к сегрегации — каждый агент был согласен на 40% «чужих» соседей. Но сумма локальных переездов выстроила глобальное разделение.

Как работает под капотом

Функция neighbors обходит окрестность Мура и считает два числа: same (сколько соседей того же типа) и diff (другого). Пустые клетки в счёт не идут — условие g[rr][cc] != 0. Функция happy переводит это в решение: если занятых соседей нет (tot == 0), агент доволен по умолчанию; иначе сравнивает долю same / tot >= thr с порогом.

Главный цикл на каждой итерации заново собирает список пустых клеток empties, проходит по всем агентам и недовольных переселяет: выбирает случайную пустую клетку random.choice(empties), ставит туда агента, освобождает старую клетку и обновляет список пустот. Флаг moved отслеживает, был ли хоть один переезд; если за итерацию никто не двинулся — система достигла равновесия, и break прерывает цикл. В нашем прогоне равновесие (ноль недовольных) наступило за 4 шага.

Заметьте: в отличие от Игры «Жизнь», здесь обновление асинхронное — агенты переезжают по очереди, и переезд одного сразу влияет на расчёт для следующих в той же итерации. Это нормально для модели Шеллинга: она про последовательные индивидуальные решения, а не про одновременный пересчёт всего поля.

Частые ошибки

  • Учитывать пустые клетки как соседей. Если в долю «своих» включать пустоты, порог станет считаться неверно. Доля берётся только от занятых соседей.
  • Деление на ноль. У агента-отшельника без занятых соседей tot == 0. Без отдельной проверки same / tot упадёт с ошибкой. Такого агента логично считать довольным.
  • Забыть обновить список пустот. После переезда старая клетка становится пустой, а новая — занятой. Если не поддерживать empties в актуальном состоянии, агенты начнут «переезжать» на занятые места.
  • Толковать модель буквально. Это абстракция про эмерджентность, а не описание реальных сообществ. Главный урок — методологический: глобальный исход может противоречить локальным намерениям.

Итоги

  • Модель Шеллинга — автомат с агентами двух абстрактных типов и пустыми клетками на сетке.
  • Агент доволен, если доля соседей своего типа не ниже порога толерантности; недовольные переезжают на случайное свободное место.
  • Даже умеренный порог (например, 40% своих) приводит к сильной глобальной сегрегации — крупным одноцветным областям.
  • Это эмерджентность: глобальный результат сильнее и неожиданнее индивидуальных намерений, в которых не было цели разделяться.
  • Модель Шеллинга — абстрактный мысленный эксперимент (Нобелевская премия 2005 года), а не утверждение о людях.
Проверьте себя
1. Какой главный, неинтуитивный вывод демонстрирует модель сегрегации Шеллинга?
AАгенты всегда хотят жить только среди своих, поэтому разделение неизбежно
BДаже умеренное предпочтение (например, 40% своих соседей) ведёт к сильной глобальной сегрегации
CПри любом пороге толерантности сетка остаётся равномерно перемешанной
DСегрегация возникает только если агенты не могут переезжать
2. Почему в функции happy отдельно проверяется случай tot == 0?
AЧтобы ускорить программу
BЧтобы избежать деления на ноль у агента без занятых соседей; такого агента считают довольным
CЧтобы посчитать пустые клетки как своих
DЭто лишняя проверка, её можно убрать
3. Чем обновление в модели Шеллинга отличается от Игры «Жизнь»?
AОно синхронное — всё поле пересчитывается одновременно
BОно асинхронное — агенты переезжают по очереди, и переезд одного сразу влияет на следующих
CВ модели Шеллинга вообще ничего не меняется
DРазличий нет