Модель громадянського насильства (civil violence) Дж. Епштейна

Хотілося б поговорити про те, як виникають та розгортаються масові заворушення. І знову нам стане в нагоді агентне моделювання, а саме – модель громадянського насильства, розроблена Дж. Епштейном. Я реалізувала її в R, але додала в неї дещо від себе😊 Про модель Епштейна можете почитати тут: https://www.pnas.org/doi/10.1073/pnas.092080199

Отже, дії та взаємодії відбуваються у середовищі, що є решіткою розміром 32 Х 32. У клітинках цієї решітки знаходяться “звичайні” агенти (далі – агенти) та поліцейські. Частина клітинок вільні. Агенти бувають активними (беруть участь в акції протесту) й неактивними (не беруть участі в акції протесту). Вони мають наступні характеристики:

Сприйняття ступеню важкості ситуації (hardship). Значення цієї характеристики змінюються від 0 до 1; кожному з агентів присвоюється випадковим чином обране значення з цього проміжку. Чим більше значення, тим більш важкою, на думку агента, є ситуація (прикладом такої ситуації може бути підвищення цін на товари першої необхідності).

Оцінка легітимності владного режиму (legitimacy). Значення цієї характеристики теж змінюються від 0 до 1. Всім агентам присвоюється однакове значення цієї характеристики. Чим більше значення, тим вища оцінка легітимності режиму.

Політичне невдоволення (grievance). Розраховується за формулою:

\[ G = H(1 - L), \] де \(\small G\) – політичне невдоволення, \(\small H\) – сприйняття ступеню важкості ситуації, \(\small L\) – оцінка легітимності режиму. Значення характеристики змінюються від 0 до 1; чим більше значення, тим вищим є політичне невдоволення.

Ступінь неприйнятності ризику для агента (risk aversion). Значення цієї характеристики змінюються від 0 до 1; кожному з агентів присвоюється випадковим чином обране значення з цього проміжку. Чим вище значення, тим менш прийнятним для агента є ризик від участі в акціях протесту.

Імовірність для агента бути затриманим (estimated arrest probability). Розраховується за наступною формулою:

\[ P = 1 - exp(-k(C_n/A_n)), \] де \(\small P\) – імовірність для агента бути затриманим, \(\small k\) – константа (становить 2.31), \(\small C_n\) – кількість поліцейських, що знаходяться в зоні видимості агента (а саме кількість поліцейських, які знаходяться у сусідніх клітинках), \(\small A_n\) – кількість активних агентів, що знаходяться в зоні видимості агента (а саме кількість активних агентів, які знаходяться у сусідніх клітинках + сам агент). Значення змінюються від 0 до 1, чим більше значення, тим вища імовірність для агента бути затриманим.

Ризики, які несе агент, якщо вийде на протест (net risk). Розраховується за формулою:

\[ N = RP, \] де \(\small N\) – ризики, що несе агент за умови, якщо вийде на протест, \(\small R\) – cтупінь неприйнятності ризику для агента, \(\small P\) – імовірність для агента бути затриманим. Значення характеристики змінюються від 0 до 1. Чим більше значення, тим вищі ризики несе агент, що вийде на протест.

Поріг. Є своєрідним стримуючим фактором, характеризує неготовність агента протестувати. Значення можуть змінюватись від -1 до 1 (чим вище значення, тим вища неготовність протестувати).

Правило активації/деактивації агента наступне. Якщо \(\small G - N > T\) (\(\small T\) – поріг), неактивний агент стає активним. Якщо ж \(\small G - N \le T\), активний агент стає неактивним. Агенти можуть переміщатися на будь-яку сусідню клітинку.

Для поліцейських діє наступне правило. Вони затримують випадково обраного активного агента, що знаходиться в одній із сусідніх клітинок. Поліцейські, так само як і агенти, можуть переміщатися на будь-яку сусідню клітинку. Активація/деактивація агентів та переміщення агентів і поліцейських відбуваються в декілька кроків (дискретних часових проміжків). Коли на решітці не залишається жодного активного агента, активація/деактивація агентів та переміщення агентів і поліцейських завершуються.

Кілька слів про оцінку легітимності владного режиму. Вона може залишатися на незмінному рівні. А може відбуватись поступова втрата режимом своєї легітимності в очах агентів. В моделі реалізовано обидва сценарії. Код моделі наведено нижче.

## В основу даної моделі покладено модель громадянського насильства (Дж. Епштейн)
# Маємо решітку 32 X 32, де знаходяться агенти і поліцейські
# Параметри моделі:
# A - кількість активних агентів (від 0 до 923)
# Q - кількість неактивних агентів (від 0 до 923)
# A + Q > 0
# C - кількість поліцейських (від 1 до 923)
# lgtm - оцінка респондентами легітимності режиму (від 0 до 1)
# change - чи відбувається втрата режимом легітимності (change = F - ні, change = T - так)
# t - поріг (від -1 до 1)
# v - візуалізація ("none" - немає, "dynamic" - візуалізація для кожного кроку, "static" - візуалізація для нульового й останнього кроків)
# res - результати (res = F - результати не виводяться, res = T - результати виводяться)
# Сума кількостей активних, неактивних, затриманих агентів, поліцейських та вільних клітинок не має перевищувати 1024
Model_cv <- function(A = 0, Q = 832, C = 92, t = 0.1, lgtm, change = F,
                     v = c("none", "dynamic", "static"),
                     res = F) {
  # Нульовий крок
  count <- 0
  # Розподіл активних (1), неактивних (2) агентів, поліцейських (3), вільних клітинок (4) 
  env <- c(rep(1, A), 
           rep(2, Q),
           rep(3, C),
           rep(4, 100))
  # Рандомізуємо
  grid <- matrix(sample(env, 1024), 32, 32)
  # Візуалізація початкового розміщення агентів
  # brown2 - активні агенти
  # darkcyan - неактивні агенти
  # gray20 - поліцейські
  # gray60 - вільні клітинки
  if (v == "static") {
    par(mfrow = c(1, 2))
    grid_col <- as.character(grid)
    grid_col[grid_col == "1"] <- "brown2"
    grid_col[grid_col == "2"] <- "darkcyan"
    grid_col[grid_col == "3"] <- "gray20"
    grid_col[grid_col == "4"] <- "gray60"
    colors <- names(table(grid_col))[order(match(names(table(grid_col)), 
                                                 c("brown2", "darkcyan", "gray20", "gray60")))]
    image(t(apply(grid, 2, rev)), 
          col = colors,
          oldstyle = T,
          axes = F, 
          asp = 1)
  }
  if (v == "dynamic") {
    par(mfrow = c(1, 1))
    grid_col <- as.character(grid)
    grid_col[grid_col == "1"] <- "brown2"
    grid_col[grid_col == "2"] <- "darkcyan"
    grid_col[grid_col == "3"] <- "gray20"
    grid_col[grid_col == "4"] <- "gray60"
    colors <- names(table(grid_col))[order(match(names(table(grid_col)), 
                                                 c("brown2", "darkcyan", "gray20", "gray60")))]
    image(t(apply(grid, 2, rev)), 
          col = colors,
          oldstyle = T,
          axes = F, 
          asp = 1)
  }
  ## Фактори, що обумовлюють поведінку агентів
  # H - Сприйняття ступеню важкості ситуації
  H <- ifelse(grid < 3, runif(length(grid[grid < 3])), NA)
  # L - Оцінка легітимності режиму
  L <- ifelse(grid < 3, lgtm, NA)
  # G - Політичне невдоволення
  G <- H * (1 - L)
  # R - Ступінь неприйнятності ризику для агента
  R <- ifelse(grid < 3, runif(length(grid[grid < 3])), NA)
  repeat {
  # Окреслимо сусідні клітинки для кожної з клітинок решітки
  index <- NULL
  for(i in 1:dim(grid)[1]) {
    ind <- which(grid == i, arr.ind = T)
    index <- rbind(index, ind)
  }
  dst <- as.matrix(dist(index, method = "maximum"))
  nb <- apply(dst, 1, function(x) grid[x == 1])
  names(nb) <- grid
  # Cq - кількість сусідів-поліцейських, Aq - Кількість сусідів, що є активними агентами
  CA <- sapply(1:length(grid), function(x) 
    table(factor(nb[[x]], levels = 1:4)))
  colnames(CA) <- grid
  Cq <- CA[3,]
  Cq <- ifelse(names(Cq) == "1" | names(Cq) == "2", Cq, NA)       
  Cq <- matrix(Cq, dim(grid)[1], dim(grid)[2])
  Aq <- CA[1,] 
  Aq <- ifelse(names(Aq) == "1" | names(Aq) == "2", Aq, NA)       
  Aq <- matrix(Aq, dim(grid)[1], dim(grid)[2])
  Aq <- Aq + 1
  # k - Константа
  k <- 2.3
  # P - Імовірність для агента бути затриманим
  P <- 1 - exp(-k * (Cq / Aq))
  # N - Ризики, які несе агент, якщо вийде на протест
  N <- R * P
  count <- count + 1
  ## Якщо різниця між політичним невдоволенням та ризиками вища за поріг (t), неактивний агент стає активним, якщо ні - активний агент стає неактивним 
  grid <- ifelse(G - N > t & grid == 2, 1, grid)
  grid <- ifelse(G - N <= t & grid == 1, 2, grid)
  # Втрата режимом легітимності
  if(change == T) {
    # L - Оцінка легітимності режиму
    L <- L - runif(1, min = 0.01, max = 0.02)
    L <- ifelse(L < 0, 0, L)
    L <- ifelse(L > 1, 1, L)
    # G - Політичне невдоволення
    G <- H * (1 - L)
  }
  ## Поліцейські затримують активних агентів, які знаходяться у сусідніх клітинках
  # Визначимо положення активних агентів
  ag_act <- which(grid == 1, arr.ind = T) 
  ag_act_2 <- which(grid == 1)
  # Визначимо положення поліцейських
  cops <- which(grid == 3, arr.ind = T)
  cops_2 <- which(grid == 3)
  # Визначимо положення активних агентів, що є сусідами поліцейських
  nbc_act <- sapply(1:length(cops_2), function(x) 
    cbind(abs(cops[x, 1] - ag_act[,2]),
          abs(cops[x, 2] - ag_act[,2])), 
    simplify = F)
  nbc_act <- sapply(1:length(nbc_act), function(x) 
    subset(ag_act_2, nbc_act[[x]][,2] <= 1 & nbc_act[[x]][,2] <= 1))
  # Випадковим чином серед активних сусідів поліцейських обираємо одного агента, якого має бути затримано
  aim_act <- sapply(1:length(nbc_act), function(x) 
    ifelse(length(nbc_act[[x]]) == 0, NA, sample(as.character(nbc_act[[x]]), 1)))
  aim_act <- aim_act[is.na(aim_act) == F]
  # Активних агентів затримують. Затримані агенти трансформуються у вільні клітинки
  grid[as.numeric(aim_act)] <- 4
  H[grid == 4] <- NA
  L[grid == 4] <- NA
  G[grid == 4] <- NA
  R[grid == 4] <- NA
  ## Поліцейські та агенти можуть переміщатися на будь-яку сусідню клітинку
  # Визначимо положення агентів та поліцейських
  agc <- rbind(which(grid == 1, arr.ind = T), 
               which(grid == 2, arr.ind = T),
               which(grid == 3, arr.ind = T))
  agc_2 <- c(which(grid == 1), which(grid == 2), which(grid == 3))
  # Визначимо положення вільних клітинок
  empty <- which(grid == 4, arr.ind = T)
  empty_2 <- which(grid == 4)
  # Визначимо сусідні вільні клітинки для кожного з агентів та поліцейських
  nb_empty <- sapply(1:length(agc_2), function(x) 
    cbind(abs(agc[x, 1] - empty[,2]),
          abs(agc[x, 2] - empty[,2])), 
    simplify = F)
  nb_empty <- sapply(1:length(nb_empty), function(x) 
    subset(empty_2, nb_empty[[x]][,2] <= 1 & nb_empty[[x]][,2] <= 1))
  # Випадковим чином визначаємо одну сусідню вільну клітинку, на яку перейдуть агенти чи поліцейські
  empty_cell <- sapply(1:length(nb_empty), function(x)
    ifelse(length(nb_empty[[x]]) == 0, NA, sample(as.character(nb_empty[[x]]), 1)))
  names(empty_cell) <- agc_2
  # Рандомізуємо
  empty_cell <- sample(empty_cell, length(empty_cell))
  # Видаляємо дублікати та NA
  move_cell <- empty_cell[duplicated(empty_cell) == F & is.na(empty_cell) == F]
  # Агенти / поліцейські переміщуються на сусідні вільні клітинки
  grid[as.numeric(move_cell)] <- grid[as.numeric(names(move_cell))]
  H[as.numeric(move_cell)] <- H[as.numeric(names(move_cell))]
  L[as.numeric(move_cell)] <- L[as.numeric(names(move_cell))]
  G[as.numeric(move_cell)] <- G[as.numeric(names(move_cell))]
  R[as.numeric(move_cell)] <- R[as.numeric(names(move_cell))]
  grid[as.numeric(names(move_cell))] <- 4
  H[as.numeric(names(move_cell))] <- NA
  L[as.numeric(names(move_cell))] <- NA
  G[as.numeric(names(move_cell))] <- NA
  R[as.numeric(names(move_cell))] <- NA
  # Візуалізація для кожного кроку
  if (v == "dynamic") {
    par(mfrow = c(1, 1))
    grid_col <- as.character(grid)
    grid_col[grid_col == "1"] <- "brown2"
    grid_col[grid_col == "2"] <- "darkcyan"
    grid_col[grid_col == "3"] <- "gray20"
    grid_col[grid_col == "4"] <- "gray60"
    colors <- names(table(grid_col))[order(match(names(table(grid_col)), 
                                                 c("brown2", "darkcyan", "gray20", "gray60")))]
    image(t(apply(grid, 2, rev)), 
          col = colors,
          oldstyle = T,
          axes = F, 
          asp = 1)
  }
  # Кількість неактивних агентів та затриманих
  if(res == T) {
  k_ag <- c(length(grid[grid == 4]) - 100,
            length(grid[grid == 2]))
  names(k_ag) <- c("Затримані", "Неактивні")
  }
  if (length(grid[grid == 1]) == 0) break
  }
  # Візуалізація для останнього кроку
  if (v == "static") {
    par(mfrow = c(1, 2))
    grid_col <- as.character(grid)
    grid_col[grid_col == "1"] <- "brown2"
    grid_col[grid_col == "2"] <- "darkcyan"
    grid_col[grid_col == "3"] <- "gray20"
    grid_col[grid_col == "4"] <- "gray60"
    colors <- names(table(grid_col))[order(match(names(table(grid_col)), 
                                                 c("brown2", "darkcyan", "gray20", "gray60")))]
    image(t(apply(grid, 2, rev)), 
          col = colors,
          oldstyle = T,
          axes = F, 
          asp = 1)
  }
  # Виведення результатів (для останнього кроку)
  if(res == T) {
    k_ag
  }
}

Тепер перейдемо до експериментування зі створеною моделлю. За замовчуванням на нульовому кроці кількість активних агентів становила 0, кількість поліцейських – 92 (приблизно 10% від загальної кількості всіх агентів та поліцейських). А кількість неактивних агентів дорівнювала 832. Кількість вільних клітинок була сталою і дорівнювала 100. Значення порогу за замовчуванням становило 0.1. Спочатку для експериментів обрали значення параметру change = F; це вказувало на те, оцінка легітимності режиму залишалась незмінною. А значення параметру lgtm (оцінка легітимності владного режиму) були наступні: 0.1, 0.3, 0.5, 0.7, 0.8, 0.89, 0.9. Для кожного з 7 значень параметру lgtm провели по 50 експериментів. Після цього обрали значення параметру change = T, що вказувало на поступову втрату легітимності владного режиму в очах агентів. А значення параметру lgtm були ті ж, що і в попередньому випадку. Для кожного з 7 значень параметру lgtm, знову ж таки, провели по 50 експериментів. Таким чином, всього було проведено 700 експериментів з моделлю. Їхні результати представлено у таблиці.

lgtm=0.1 lgtm=0.3 lgtm=0.5 lgtm=0.7 lgtm=0.8 lgtm=0.89 lgtm=0.9
Середня кількість затриманих агентів
Оцінка легітимності режиму незмінна 736,82 709,42 665,30 554,18 409,94 70,46 0,00
Поступова втрата легітимності 746,54 746,72 743,96 744,34 744,32 744,14 0,00
Середня кількість неактивних агентів
Оцінка легітимності режиму незмінна 95,18 122,58 166,70 277,82 422,06 761,54 832,00
Поступова втрата легітимності 85,46 85,28 88,04 87,66 87,68 87,86 832,00
Note: Середня кількість затриманих агентів та середня кількість неактивних агентів розраховувались за результатами проведених експериментів для кожної комбінації значень параметрів lgtm (вказаних у першому рядку таблиці) та change. Для кожної комбінації значень параметрів lgtm та change було проведено 50 експериментів

Розглянемо ситуацію, коли оцінка агентами легітимності режиму залишалась незмінною. Чим вища оцінка агентами легітимності режиму, тим менша середня кількість затриманих та більша середня кількість неактивних агентів, тих, хто не виходить на протест. Якщо ж мало місце поступове зниження легітимності режиму в очах агентів, отримали наступне. Можемо бачити, що і для низьких, і для високих значень оцінки легітимності режиму середня кількість затриманих та неактивних агентів є, фактично, однаковою. Однак це діє за умови, якщо оцінка легітимності режиму не перевищує 0,9.

Можете поспостерігати, як відбувається процес затримання активних агентів за умови, якщо lgtm = 0.8. Активних агентів позначено червоним кольором, неактивних – блакитним. Поліцейські мають темно-сірий (майже чорний) колір, а вільні клітинки сірого кольору. Спочатку є лише неактивні агенти, вільні клітинки та поліцейські. На останньому кроці залишаються лише неактивні агенти (їх менше, ніж на початку), вільні клітинки (в більшій кількості, ніж на початку) та поліцейські.

# оцінка агентами легітимності режиму незмінна
Model_cv(lgtm = 0.8, change = F, v = "dynamic")

# оцінка агентами легітимності режиму незмінна
Model_cv(lgtm = 0.8, change = T, v = "dynamic")


  1. Див. тут: http://ceur-ws.org/Vol-1113/paper10.pdf↩︎