Mesa 3.0.0b0__py3-none-any.whl → 3.0.0b2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of Mesa might be problematic. Click here for more details.

Files changed (85) hide show
  1. mesa/__init__.py +2 -1
  2. mesa/agent.py +37 -27
  3. mesa/examples/README.md +37 -0
  4. mesa/examples/__init__.py +21 -0
  5. mesa/examples/advanced/__init__.py +0 -0
  6. mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +116 -0
  7. mesa/examples/advanced/epstein_civil_violence/Readme.md +34 -0
  8. mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
  9. mesa/examples/advanced/epstein_civil_violence/agents.py +158 -0
  10. mesa/examples/advanced/epstein_civil_violence/app.py +72 -0
  11. mesa/examples/advanced/epstein_civil_violence/model.py +146 -0
  12. mesa/examples/advanced/pd_grid/Readme.md +43 -0
  13. mesa/examples/advanced/pd_grid/__init__.py +0 -0
  14. mesa/examples/advanced/pd_grid/agents.py +50 -0
  15. mesa/examples/advanced/pd_grid/analysis.ipynb +228 -0
  16. mesa/examples/advanced/pd_grid/app.py +50 -0
  17. mesa/examples/advanced/pd_grid/model.py +71 -0
  18. mesa/examples/advanced/sugarscape_g1mt/Readme.md +64 -0
  19. mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
  20. mesa/examples/advanced/sugarscape_g1mt/agents.py +344 -0
  21. mesa/examples/advanced/sugarscape_g1mt/app.py +70 -0
  22. mesa/examples/advanced/sugarscape_g1mt/model.py +180 -0
  23. mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +50 -0
  24. mesa/examples/advanced/sugarscape_g1mt/tests.py +69 -0
  25. mesa/examples/advanced/wolf_sheep/Readme.md +57 -0
  26. mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
  27. mesa/examples/advanced/wolf_sheep/agents.py +102 -0
  28. mesa/examples/advanced/wolf_sheep/app.py +77 -0
  29. mesa/examples/advanced/wolf_sheep/model.py +137 -0
  30. mesa/examples/basic/__init__.py +0 -0
  31. mesa/examples/basic/boid_flockers/Readme.md +22 -0
  32. mesa/examples/basic/boid_flockers/__init__.py +0 -0
  33. mesa/examples/basic/boid_flockers/agents.py +71 -0
  34. mesa/examples/basic/boid_flockers/app.py +58 -0
  35. mesa/examples/basic/boid_flockers/model.py +69 -0
  36. mesa/examples/basic/boltzmann_wealth_model/Readme.md +56 -0
  37. mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
  38. mesa/examples/basic/boltzmann_wealth_model/agents.py +31 -0
  39. mesa/examples/basic/boltzmann_wealth_model/app.py +65 -0
  40. mesa/examples/basic/boltzmann_wealth_model/model.py +43 -0
  41. mesa/examples/basic/boltzmann_wealth_model/st_app.py +115 -0
  42. mesa/examples/basic/conways_game_of_life/Readme.md +39 -0
  43. mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
  44. mesa/examples/basic/conways_game_of_life/agents.py +47 -0
  45. mesa/examples/basic/conways_game_of_life/app.py +39 -0
  46. mesa/examples/basic/conways_game_of_life/model.py +31 -0
  47. mesa/examples/basic/conways_game_of_life/st_app.py +72 -0
  48. mesa/examples/basic/schelling/Readme.md +40 -0
  49. mesa/examples/basic/schelling/__init__.py +0 -0
  50. mesa/examples/basic/schelling/agents.py +26 -0
  51. mesa/examples/basic/schelling/analysis.ipynb +205 -0
  52. mesa/examples/basic/schelling/app.py +42 -0
  53. mesa/examples/basic/schelling/model.py +59 -0
  54. mesa/examples/basic/virus_on_network/Readme.md +61 -0
  55. mesa/examples/basic/virus_on_network/__init__.py +0 -0
  56. mesa/examples/basic/virus_on_network/agents.py +69 -0
  57. mesa/examples/basic/virus_on_network/app.py +136 -0
  58. mesa/examples/basic/virus_on_network/model.py +96 -0
  59. mesa/experimental/__init__.py +8 -2
  60. mesa/experimental/cell_space/cell.py +9 -0
  61. mesa/experimental/cell_space/discrete_space.py +13 -1
  62. mesa/experimental/cell_space/grid.py +13 -0
  63. mesa/experimental/cell_space/network.py +3 -0
  64. mesa/experimental/devs/eventlist.py +6 -0
  65. mesa/model.py +76 -12
  66. mesa/space.py +70 -5
  67. mesa/time.py +5 -3
  68. mesa/visualization/components/altair.py +87 -19
  69. mesa/visualization/components/matplotlib.py +65 -16
  70. mesa/visualization/solara_viz.py +13 -58
  71. {mesa-3.0.0b0.dist-info → mesa-3.0.0b2.dist-info}/METADATA +1 -3
  72. mesa-3.0.0b2.dist-info/RECORD +93 -0
  73. mesa/cookiecutter-mesa/cookiecutter.json +0 -8
  74. mesa/cookiecutter-mesa/hooks/post_gen_project.py +0 -13
  75. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +0 -4
  76. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +0 -27
  77. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +0 -11
  78. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/__init__.py +0 -1
  79. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +0 -60
  80. mesa/main.py +0 -65
  81. mesa-3.0.0b0.dist-info/RECORD +0 -45
  82. {mesa-3.0.0b0.dist-info → mesa-3.0.0b2.dist-info}/WHEEL +0 -0
  83. {mesa-3.0.0b0.dist-info → mesa-3.0.0b2.dist-info}/entry_points.txt +0 -0
  84. {mesa-3.0.0b0.dist-info → mesa-3.0.0b2.dist-info}/licenses/LICENSE +0 -0
  85. {mesa-3.0.0b0.dist-info → mesa-3.0.0b2.dist-info}/licenses/NOTICE +0 -0
mesa/__init__.py CHANGED
@@ -5,6 +5,7 @@ Core Objects: Model, and Agent.
5
5
 
6
6
  import datetime
7
7
 
8
+ import mesa.experimental as experimental
8
9
  import mesa.space as space
9
10
  import mesa.time as time
10
11
  from mesa.agent import Agent
@@ -23,7 +24,7 @@ __all__ = [
23
24
  ]
24
25
 
25
26
  __title__ = "mesa"
26
- __version__ = "3.0.0b0"
27
+ __version__ = "3.0.0b2"
27
28
  __license__ = "Apache 2.0"
28
29
  _this_year = datetime.datetime.now(tz=datetime.timezone.utc).date().year
29
30
  __copyright__ = f"Copyright {_this_year} Project Mesa Team"
mesa/agent.py CHANGED
@@ -21,6 +21,8 @@ from random import Random
21
21
  # mypy
22
22
  from typing import TYPE_CHECKING, Any, Literal, overload
23
23
 
24
+ import numpy as np
25
+
24
26
  if TYPE_CHECKING:
25
27
  # We ensure that these are not imported during runtime to prevent cyclic
26
28
  # dependency.
@@ -62,12 +64,18 @@ class Agent:
62
64
  super().__init__(*args, **kwargs)
63
65
 
64
66
  self.model: Model = model
65
- self.model.register_agent(self)
66
67
  self.unique_id: int = next(self._ids[model])
67
68
  self.pos: Position | None = None
69
+ self.model.register_agent(self)
68
70
 
69
71
  def remove(self) -> None:
70
- """Remove and delete the agent from the model."""
72
+ """Remove and delete the agent from the model.
73
+
74
+ Notes:
75
+ If you need to do additional cleanup when removing an agent by for example removing
76
+ it from a space, consider extending this method in your own agent class.
77
+
78
+ """
71
79
  with contextlib.suppress(KeyError):
72
80
  self.model.deregister_agent(self)
73
81
 
@@ -79,9 +87,14 @@ class Agent:
79
87
 
80
88
  @property
81
89
  def random(self) -> Random:
82
- """Return a seeded rng."""
90
+ """Return a seeded stdlib rng."""
83
91
  return self.model.random
84
92
 
93
+ @property
94
+ def rng(self) -> np.random.Generator:
95
+ """Return a seeded np.random rng."""
96
+ return self.model.rng
97
+
85
98
 
86
99
  class AgentSet(MutableSet, Sequence):
87
100
  """A collection class that represents an ordered set of agents within an agent-based model (ABM).
@@ -99,14 +112,18 @@ class AgentSet(MutableSet, Sequence):
99
112
  which means that agents not referenced elsewhere in the program may be automatically removed from the AgentSet.
100
113
  """
101
114
 
102
- def __init__(self, agents: Iterable[Agent], model: Model):
115
+ def __init__(self, agents: Iterable[Agent], random: Random | None = None):
103
116
  """Initializes the AgentSet with a collection of agents and a reference to the model.
104
117
 
105
118
  Args:
106
119
  agents (Iterable[Agent]): An iterable of Agent objects to be included in the set.
107
- model (Model): The ABM model instance to which this AgentSet belongs.
120
+ random (Random): the random number generator
108
121
  """
109
- self.model = model
122
+ if random is None:
123
+ random = (
124
+ Random()
125
+ ) # FIXME see issue 1981, how to get the central rng from model
126
+ self.random = random
110
127
  self._agents = weakref.WeakKeyDictionary({agent: None for agent in agents})
111
128
 
112
129
  def __len__(self) -> int:
@@ -177,7 +194,7 @@ class AgentSet(MutableSet, Sequence):
177
194
 
178
195
  agents = agent_generator(filter_func, agent_type, at_most)
179
196
 
180
- return AgentSet(agents, self.model) if not inplace else self._update(agents)
197
+ return AgentSet(agents, self.random) if not inplace else self._update(agents)
181
198
 
182
199
  def shuffle(self, inplace: bool = False) -> AgentSet:
183
200
  """Randomly shuffle the order of agents in the AgentSet.
@@ -200,7 +217,7 @@ class AgentSet(MutableSet, Sequence):
200
217
  return self
201
218
  else:
202
219
  return AgentSet(
203
- (agent for ref in weakrefs if (agent := ref()) is not None), self.model
220
+ (agent for ref in weakrefs if (agent := ref()) is not None), self.random
204
221
  )
205
222
 
206
223
  def sort(
@@ -225,7 +242,7 @@ class AgentSet(MutableSet, Sequence):
225
242
  sorted_agents = sorted(self._agents.keys(), key=key, reverse=not ascending)
226
243
 
227
244
  return (
228
- AgentSet(sorted_agents, self.model)
245
+ AgentSet(sorted_agents, self.random)
229
246
  if not inplace
230
247
  else self._update(sorted_agents)
231
248
  )
@@ -284,15 +301,17 @@ class AgentSet(MutableSet, Sequence):
284
301
 
285
302
  It's a fast, optimized version of calling shuffle() followed by do().
286
303
  """
287
- agents = list(self._agents.keys())
288
- self.random.shuffle(agents)
304
+ weakrefs = list(self._agents.keyrefs())
305
+ self.random.shuffle(weakrefs)
289
306
 
290
307
  if isinstance(method, str):
291
- for agent in agents:
292
- getattr(agent, method)(*args, **kwargs)
308
+ for ref in weakrefs:
309
+ if (agent := ref()) is not None:
310
+ getattr(agent, method)(*args, **kwargs)
293
311
  else:
294
- for agent in agents:
295
- method(agent, *args, **kwargs)
312
+ for ref in weakrefs:
313
+ if (agent := ref()) is not None:
314
+ method(agent, *args, **kwargs)
296
315
 
297
316
  return self
298
317
 
@@ -477,7 +496,7 @@ class AgentSet(MutableSet, Sequence):
477
496
  Returns:
478
497
  dict: A dictionary representing the state of the AgentSet.
479
498
  """
480
- return {"agents": list(self._agents.keys()), "model": self.model}
499
+ return {"agents": list(self._agents.keys()), "random": self.random}
481
500
 
482
501
  def __setstate__(self, state):
483
502
  """Set the state of the AgentSet during deserialization.
@@ -485,18 +504,9 @@ class AgentSet(MutableSet, Sequence):
485
504
  Args:
486
505
  state (dict): A dictionary representing the state to restore.
487
506
  """
488
- self.model = state["model"]
507
+ self.random = state["random"]
489
508
  self._update(state["agents"])
490
509
 
491
- @property
492
- def random(self) -> Random:
493
- """Provide access to the model's random number generator.
494
-
495
- Returns:
496
- Random: The random number generator associated with the model.
497
- """
498
- return self.model.random
499
-
500
510
  def groupby(self, by: Callable | str, result_type: str = "agentset") -> GroupBy:
501
511
  """Group agents by the specified attribute or return from the callable.
502
512
 
@@ -529,7 +539,7 @@ class AgentSet(MutableSet, Sequence):
529
539
 
530
540
  if result_type == "agentset":
531
541
  return GroupBy(
532
- {k: AgentSet(v, model=self.model) for k, v in groups.items()}
542
+ {k: AgentSet(v, random=self.random) for k, v in groups.items()}
533
543
  )
534
544
  else:
535
545
  return GroupBy(groups)
@@ -0,0 +1,37 @@
1
+ # Mesa core examples
2
+ This folder contains a collection of example models built using Mesa. These core models are maintained by the Mesa team and are intended to demonstrate the capabilities of Mesa.
3
+
4
+ More user examples and showcases can be found in the [mesa-examples](https://github.com/projectmesa/mesa-examples) repository.
5
+
6
+ ## Basic Examples
7
+ The basic examples are relatively simple and only use stable Mesa features. They are good starting points for learning how to use Mesa.
8
+
9
+ ### [Boltzmann Wealth Model](basic/boltzmann_wealth_model)
10
+ Completed code to go along with the [tutorial](https://mesa.readthedocs.io/latest/tutorials/intro_tutorial.html) on making a simple model of how a highly-skewed wealth distribution can emerge from simple rules.
11
+
12
+ ### [Boids Flockers Model](basic/boid_flockers)
13
+ [Boids](https://en.wikipedia.org/wiki/Boids)-style flocking model, demonstrating the use of agents moving through a continuous space following direction vectors.
14
+
15
+ ### [Conway's Game of Life](basic/conways_game_of_life)
16
+ Implementation of [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life), a cellular automata where simple rules can give rise to complex patterns.
17
+
18
+ ### [Schelling Segregation Model](basic/schelling)
19
+ Mesa implementation of the classic [Schelling segregation](http://nifty.stanford.edu/2014/mccown-schelling-model-segregation/) model.
20
+
21
+ ### [Virus on a Network Model](basic/virus_on_network)
22
+ This model is based on the NetLogo [Virus on a Network](https://ccl.northwestern.edu/netlogo/models/VirusonaNetwork) model.
23
+
24
+ ## Advanced Examples
25
+ The advanced examples are more complex and may use experimental Mesa features. They are good starting points for learning how to build more complex models.
26
+
27
+ ### [Epstein Civil Violence Model](advanced/epstein_civil_violence)
28
+ Joshua Epstein's [model](http://www.uvm.edu/~pdodds/files/papers/others/2002/epstein2002a.pdf) of how a decentralized uprising can be suppressed or reach a critical mass of support.
29
+
30
+ ### [Demographic Prisoner's Dilemma on a Grid](advanced/pd_grid)
31
+ Grid-based demographic prisoner's dilemma model, demonstrating how simple rules can lead to the emergence of widespread cooperation -- and how a model activation regime can change its outcome.
32
+
33
+ ### [Sugarscape Model with Traders](advanced/sugarscape_g1mt)
34
+ This is Epstein & Axtell's Sugarscape model with Traders, a detailed description is in Chapter four of *Growing Artificial Societies: Social Science from the Bottom Up (1996)*. The model shows how emergent price equilibrium can happen via decentralized dynamics.
35
+
36
+ ### [Wolf-Sheep Predation Model](advanced/wolf_sheep)
37
+ Implementation of an ecological model of predation and reproduction, based on the NetLogo [Wolf Sheep Predation](http://ccl.northwestern.edu/netlogo/models/WolfSheepPredation) model.
@@ -0,0 +1,21 @@
1
+ from mesa.examples.advanced.epstein_civil_violence.model import EpsteinCivilViolence
2
+ from mesa.examples.advanced.pd_grid.model import PdGrid
3
+ from mesa.examples.advanced.sugarscape_g1mt.model import SugarscapeG1mt
4
+ from mesa.examples.advanced.wolf_sheep.model import WolfSheep
5
+ from mesa.examples.basic.boid_flockers.model import BoidFlockers
6
+ from mesa.examples.basic.boltzmann_wealth_model.model import BoltzmannWealthModel
7
+ from mesa.examples.basic.conways_game_of_life.model import ConwaysGameOfLife
8
+ from mesa.examples.basic.schelling.model import Schelling
9
+ from mesa.examples.basic.virus_on_network.model import VirusOnNetwork
10
+
11
+ __all__ = [
12
+ "BoidFlockers",
13
+ "BoltzmannWealthModel",
14
+ "ConwaysGameOfLife",
15
+ "Schelling",
16
+ "VirusOnNetwork",
17
+ "EpsteinCivilViolence",
18
+ "PdGrid",
19
+ "SugarscapeG1mt",
20
+ "WolfSheep",
21
+ ]
File without changes
@@ -0,0 +1,116 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "This example implements the first model from \"Modeling civil violence: An agent-based computational approach,\" by Joshua Epstein. The paper (pdf) can be found [here](http://www.uvm.edu/~pdodds/files/papers/others/2002/epstein2002a.pdf).\n",
8
+ "\n",
9
+ "The model consists of two types of agents: \"Citizens\" (called \"Agents\" in the paper) and \"Cops.\" Agents decide whether or not to rebel by weighing their unhappiness ('grievance') against the risk of rebelling, which they estimate by comparing the local ratio of rebels to cops. \n",
10
+ "\n",
11
+ "\n"
12
+ ]
13
+ },
14
+ {
15
+ "cell_type": "code",
16
+ "execution_count": 5,
17
+ "metadata": {},
18
+ "outputs": [],
19
+ "source": [
20
+ "%matplotlib inline\n",
21
+ "\n",
22
+ "from epstein_civil_violence.model import EpsteinCivilViolence"
23
+ ]
24
+ },
25
+ {
26
+ "cell_type": "code",
27
+ "execution_count": 6,
28
+ "metadata": {},
29
+ "outputs": [],
30
+ "source": [
31
+ "model = EpsteinCivilViolence(\n",
32
+ " height=40,\n",
33
+ " width=40,\n",
34
+ " citizen_density=0.7,\n",
35
+ " cop_density=0.074,\n",
36
+ " citizen_vision=7,\n",
37
+ " cop_vision=7,\n",
38
+ " legitimacy=0.8,\n",
39
+ " max_jail_term=1000,\n",
40
+ " max_iters=1000,\n",
41
+ ") # cap the number of steps the model takes\n",
42
+ "model.run_model()"
43
+ ]
44
+ },
45
+ {
46
+ "cell_type": "markdown",
47
+ "metadata": {},
48
+ "source": [
49
+ "The model's data collector counts the number of citizens who are Active (in rebellion), Jailed, or Quiescent after each step."
50
+ ]
51
+ },
52
+ {
53
+ "cell_type": "code",
54
+ "execution_count": 7,
55
+ "metadata": {},
56
+ "outputs": [],
57
+ "source": [
58
+ "model_out = model.datacollector.get_model_vars_dataframe()"
59
+ ]
60
+ },
61
+ {
62
+ "cell_type": "code",
63
+ "execution_count": 8,
64
+ "metadata": {},
65
+ "outputs": [
66
+ {
67
+ "data": {
68
+ "image/png": "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\n",
69
+ "text/plain": [
70
+ "<Figure size 432x288 with 1 Axes>"
71
+ ]
72
+ },
73
+ "metadata": {
74
+ "needs_background": "light"
75
+ },
76
+ "output_type": "display_data"
77
+ }
78
+ ],
79
+ "source": [
80
+ "ax = model_out.plot()\n",
81
+ "ax.set_title(\"Citizen Condition Over Time\")\n",
82
+ "ax.set_xlabel(\"Step\")\n",
83
+ "ax.set_ylabel(\"Number of Citizens\")\n",
84
+ "_ = ax.legend(bbox_to_anchor=(1.35, 1.025))"
85
+ ]
86
+ },
87
+ {
88
+ "cell_type": "code",
89
+ "execution_count": null,
90
+ "metadata": {},
91
+ "outputs": [],
92
+ "source": []
93
+ }
94
+ ],
95
+ "metadata": {
96
+ "kernelspec": {
97
+ "display_name": "Python 3",
98
+ "language": "python",
99
+ "name": "python3"
100
+ },
101
+ "language_info": {
102
+ "codemirror_mode": {
103
+ "name": "ipython",
104
+ "version": 3
105
+ },
106
+ "file_extension": ".py",
107
+ "mimetype": "text/x-python",
108
+ "name": "python",
109
+ "nbconvert_exporter": "python",
110
+ "pygments_lexer": "ipython3",
111
+ "version": "3.7.3"
112
+ }
113
+ },
114
+ "nbformat": 4,
115
+ "nbformat_minor": 1
116
+ }
@@ -0,0 +1,34 @@
1
+ # Epstein Civil Violence Model
2
+
3
+ ## Summary
4
+
5
+ This model is based on Joshua Epstein's simulation of how civil unrest grows and is suppressed. Citizen agents wander the grid randomly, and are endowed with individual risk aversion and hardship levels; there is also a universal regime legitimacy value. There are also Cop agents, who work on behalf of the regime. Cops arrest Citizens who are actively rebelling; Citizens decide whether to rebel based on their hardship and the regime legitimacy, and their perceived probability of arrest.
6
+
7
+ The model generates mass uprising as self-reinforcing processes: if enough agents are rebelling, the probability of any individual agent being arrested is reduced, making more agents more likely to join the uprising. However, the more rebelling Citizens the Cops arrest, the less likely additional agents become to join.
8
+
9
+ ## How to Run
10
+
11
+ To run the model interactively, run ``EpsteinCivilViolenceServer.py`` in this directory. e.g.
12
+
13
+ ```
14
+ $ python EpsteinCivilViolenceServer.py
15
+ ```
16
+
17
+ Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run.
18
+
19
+ ## Files
20
+
21
+ * ``model.py``: Core model code.
22
+ * ``agent.py``: Agent classes.
23
+ * ``app.py``: Sets up the interactive visualization.
24
+ * ``Epstein Civil Violence.ipynb``: Jupyter notebook conducting some preliminary analysis of the model.
25
+
26
+ ## Further Reading
27
+
28
+ This model is based adapted from:
29
+
30
+ [Epstein, J. “Modeling civil violence: An agent-based computational approach”, Proceedings of the National Academy of Sciences, Vol. 99, Suppl. 3, May 14, 2002](http://www.pnas.org/content/99/suppl.3/7243.short)
31
+
32
+ A similar model is also included with NetLogo:
33
+
34
+ Wilensky, U. (2004). NetLogo Rebellion model. http://ccl.northwestern.edu/netlogo/models/Rebellion. Center for Connected Learning and Computer-Based Modeling, Northwestern University, Evanston, IL.
@@ -0,0 +1,158 @@
1
+ import math
2
+
3
+ import mesa
4
+
5
+
6
+ class EpsteinAgent(mesa.experimental.cell_space.CellAgent):
7
+ def update_neighbors(self):
8
+ """
9
+ Look around and see who my neighbors are
10
+ """
11
+ self.neighborhood = self.cell.get_neighborhood(radius=self.vision)
12
+
13
+ self.neighbors = self.neighborhood.agents
14
+ self.empty_neighbors = [c for c in self.neighborhood if c.is_empty]
15
+
16
+
17
+ class Citizen(EpsteinAgent):
18
+ """
19
+ A member of the general population, may or may not be in active rebellion.
20
+ Summary of rule: If grievance - risk > threshold, rebel.
21
+
22
+ Attributes:
23
+ hardship: Agent's 'perceived hardship (i.e., physical or economic
24
+ privation).' Exogenous, drawn from U(0,1).
25
+ regime_legitimacy: Agent's perception of regime legitimacy, equal
26
+ across agents. Exogenous.
27
+ risk_aversion: Exogenous, drawn from U(0,1).
28
+ threshold: if (grievance - (risk_aversion * arrest_probability)) >
29
+ threshold, go/remain Active
30
+ vision: number of cells in each direction (N, S, E and W) that agent
31
+ can inspect
32
+ condition: Can be "Quiescent" or "Active;" deterministic function of
33
+ greivance, perceived risk, and
34
+ grievance: deterministic function of hardship and regime_legitimacy;
35
+ how aggrieved is agent at the regime?
36
+ arrest_probability: agent's assessment of arrest probability, given
37
+ rebellion
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ model,
43
+ hardship,
44
+ regime_legitimacy,
45
+ risk_aversion,
46
+ threshold,
47
+ vision,
48
+ ):
49
+ """
50
+ Create a new Citizen.
51
+ Args:
52
+ model: the model to which the agent belongs
53
+ hardship: Agent's 'perceived hardship (i.e., physical or economic
54
+ privation).' Exogenous, drawn from U(0,1).
55
+ regime_legitimacy: Agent's perception of regime legitimacy, equal
56
+ across agents. Exogenous.
57
+ risk_aversion: Exogenous, drawn from U(0,1).
58
+ threshold: if (grievance - (risk_aversion * arrest_probability)) >
59
+ threshold, go/remain Active
60
+ vision: number of cells in each direction (N, S, E and W) that
61
+ agent can inspect. Exogenous.
62
+ model: model instance
63
+ """
64
+ super().__init__(model)
65
+ self.hardship = hardship
66
+ self.regime_legitimacy = regime_legitimacy
67
+ self.risk_aversion = risk_aversion
68
+ self.threshold = threshold
69
+ self.condition = "Quiescent"
70
+ self.vision = vision
71
+ self.jail_sentence = 0
72
+ self.grievance = self.hardship * (1 - self.regime_legitimacy)
73
+ self.arrest_probability = None
74
+
75
+ def step(self):
76
+ """
77
+ Decide whether to activate, then move if applicable.
78
+ """
79
+ if self.jail_sentence:
80
+ self.jail_sentence -= 1
81
+ return # no other changes or movements if agent is in jail.
82
+ self.update_neighbors()
83
+ self.update_estimated_arrest_probability()
84
+ net_risk = self.risk_aversion * self.arrest_probability
85
+ if self.grievance - net_risk > self.threshold:
86
+ self.condition = "Active"
87
+ else:
88
+ self.condition = "Quiescent"
89
+
90
+ if self.model.movement and self.empty_neighbors:
91
+ new_cell = self.random.choice(self.empty_neighbors)
92
+ self.move_to(new_cell)
93
+
94
+ def update_estimated_arrest_probability(self):
95
+ """
96
+ Based on the ratio of cops to actives in my neighborhood, estimate the
97
+ p(Arrest | I go active).
98
+ """
99
+ cops_in_vision = len([c for c in self.neighbors if isinstance(c, Cop)])
100
+ actives_in_vision = 1.0 # citizen counts herself
101
+ for c in self.neighbors:
102
+ if (
103
+ isinstance(c, Citizen)
104
+ and c.condition == "Active"
105
+ and c.jail_sentence == 0
106
+ ):
107
+ actives_in_vision += 1
108
+ self.arrest_probability = 1 - math.exp(
109
+ -1 * self.model.arrest_prob_constant * (cops_in_vision / actives_in_vision)
110
+ )
111
+
112
+
113
+ class Cop(EpsteinAgent):
114
+ """
115
+ A cop for life. No defection.
116
+ Summary of rule: Inspect local vision and arrest a random active agent.
117
+
118
+ Attributes:
119
+ unique_id: unique int
120
+ x, y: Grid coordinates
121
+ vision: number of cells in each direction (N, S, E and W) that cop is
122
+ able to inspect
123
+ """
124
+
125
+ def __init__(self, model, vision):
126
+ """
127
+ Create a new Cop.
128
+ Args:
129
+ x, y: Grid coordinates
130
+ vision: number of cells in each direction (N, S, E and W) that
131
+ agent can inspect. Exogenous.
132
+ model: model instance
133
+ """
134
+ super().__init__(model)
135
+ self.vision = vision
136
+
137
+ def step(self):
138
+ """
139
+ Inspect local vision and arrest a random active agent. Move if
140
+ applicable.
141
+ """
142
+ self.update_neighbors()
143
+ active_neighbors = []
144
+ for agent in self.neighbors:
145
+ if (
146
+ isinstance(agent, Citizen)
147
+ and agent.condition == "Active"
148
+ and agent.jail_sentence == 0
149
+ ):
150
+ active_neighbors.append(agent)
151
+ if active_neighbors:
152
+ arrestee = self.random.choice(active_neighbors)
153
+ sentence = self.random.randint(0, self.model.max_jail_term)
154
+ arrestee.jail_sentence = sentence
155
+ arrestee.condition = "Quiescent"
156
+ if self.model.movement and self.empty_neighbors:
157
+ new_pos = self.random.choice(self.empty_neighbors)
158
+ self.move_to(new_pos)