Mesa 3.0.0b1__py3-none-any.whl → 3.0.0rc0__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 (111) hide show
  1. mesa/__init__.py +1 -3
  2. mesa/agent.py +23 -8
  3. mesa/batchrunner.py +26 -1
  4. {examples → mesa/examples}/README.md +11 -11
  5. mesa/examples/__init__.py +21 -0
  6. {examples → mesa/examples}/advanced/epstein_civil_violence/Readme.md +3 -2
  7. examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py → mesa/examples/advanced/epstein_civil_violence/agents.py +44 -38
  8. mesa/examples/advanced/epstein_civil_violence/app.py +73 -0
  9. mesa/examples/advanced/epstein_civil_violence/model.py +114 -0
  10. examples/advanced/pd_grid/readme.md → mesa/examples/advanced/pd_grid/Readme.md +4 -3
  11. mesa/examples/advanced/pd_grid/app.py +54 -0
  12. {examples/advanced/pd_grid → mesa/examples/advanced}/pd_grid/model.py +1 -2
  13. {examples → mesa/examples}/advanced/sugarscape_g1mt/Readme.md +6 -29
  14. examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py → mesa/examples/advanced/sugarscape_g1mt/agents.py +26 -3
  15. {examples → mesa/examples}/advanced/sugarscape_g1mt/app.py +19 -18
  16. {examples/advanced/sugarscape_g1mt → mesa/examples/advanced}/sugarscape_g1mt/model.py +6 -6
  17. {examples → mesa/examples}/advanced/sugarscape_g1mt/tests.py +3 -6
  18. mesa/examples/advanced/wolf_sheep/app.py +84 -0
  19. {examples/advanced/wolf_sheep → mesa/examples/advanced}/wolf_sheep/model.py +9 -8
  20. mesa/examples/basic/boid_flockers/Readme.md +22 -0
  21. {examples → mesa/examples}/basic/boid_flockers/app.py +3 -4
  22. {examples → mesa/examples}/basic/boid_flockers/model.py +1 -2
  23. {examples → mesa/examples}/basic/boltzmann_wealth_model/Readme.md +1 -5
  24. mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
  25. {examples → mesa/examples}/basic/boltzmann_wealth_model/app.py +15 -12
  26. {examples → mesa/examples}/basic/boltzmann_wealth_model/model.py +3 -4
  27. {examples → mesa/examples}/basic/conways_game_of_life/Readme.md +11 -7
  28. mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
  29. {examples → mesa/examples}/basic/conways_game_of_life/agents.py +8 -8
  30. mesa/examples/basic/conways_game_of_life/app.py +51 -0
  31. {examples → mesa/examples}/basic/conways_game_of_life/model.py +3 -4
  32. {examples → mesa/examples}/basic/conways_game_of_life/st_app.py +2 -1
  33. examples/basic/schelling/README.md → mesa/examples/basic/schelling/Readme.md +2 -9
  34. mesa/examples/basic/schelling/__init__.py +0 -0
  35. {examples → mesa/examples}/basic/schelling/app.py +6 -7
  36. {examples → mesa/examples}/basic/schelling/model.py +1 -2
  37. mesa/examples/basic/virus_on_network/__init__.py +0 -0
  38. mesa/examples/basic/virus_on_network/app.py +114 -0
  39. {examples → mesa/examples}/basic/virus_on_network/model.py +4 -7
  40. mesa/experimental/cell_space/discrete_space.py +6 -0
  41. mesa/experimental/devs/eventlist.py +6 -0
  42. mesa/model.py +13 -0
  43. mesa/space.py +70 -35
  44. mesa/visualization/__init__.py +16 -5
  45. mesa/visualization/components/__init__.py +83 -0
  46. mesa/visualization/components/altair_components.py +188 -0
  47. mesa/visualization/components/matplotlib_components.py +176 -0
  48. mesa/visualization/mpl_space_drawing.py +558 -0
  49. mesa/visualization/solara_viz.py +30 -20
  50. {mesa-3.0.0b1.dist-info → mesa-3.0.0rc0.dist-info}/METADATA +1 -3
  51. mesa-3.0.0rc0.dist-info/RECORD +95 -0
  52. examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +0 -146
  53. examples/advanced/epstein_civil_violence/epstein_civil_violence/portrayal.py +0 -33
  54. examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py +0 -81
  55. examples/advanced/epstein_civil_violence/requirements.txt +0 -3
  56. examples/advanced/epstein_civil_violence/run.py +0 -3
  57. examples/advanced/pd_grid/pd_grid/portrayal.py +0 -19
  58. examples/advanced/pd_grid/pd_grid/server.py +0 -21
  59. examples/advanced/pd_grid/requirements.txt +0 -3
  60. examples/advanced/pd_grid/run.py +0 -3
  61. examples/advanced/sugarscape_g1mt/requirements.txt +0 -6
  62. examples/advanced/sugarscape_g1mt/run.py +0 -105
  63. examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py +0 -26
  64. examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py +0 -61
  65. examples/advanced/wolf_sheep/requirements.txt +0 -1
  66. examples/advanced/wolf_sheep/run.py +0 -3
  67. examples/advanced/wolf_sheep/wolf_sheep/resources/sheep.png +0 -0
  68. examples/advanced/wolf_sheep/wolf_sheep/resources/wolf.png +0 -0
  69. examples/advanced/wolf_sheep/wolf_sheep/server.py +0 -78
  70. examples/basic/__init__.py +0 -13
  71. examples/basic/boid_flockers/Readme.md +0 -43
  72. examples/basic/conways_game_of_life/portrayal.py +0 -18
  73. examples/basic/conways_game_of_life/requirements.txt +0 -1
  74. examples/basic/conways_game_of_life/server.py +0 -11
  75. examples/basic/virus_on_network/app.py +0 -133
  76. mesa/cookiecutter-mesa/cookiecutter.json +0 -8
  77. mesa/cookiecutter-mesa/hooks/post_gen_project.py +0 -13
  78. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +0 -4
  79. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +0 -27
  80. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +0 -11
  81. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/__init__.py +0 -1
  82. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +0 -60
  83. mesa/examples.py +0 -3
  84. mesa/main.py +0 -65
  85. mesa/visualization/components/altair.py +0 -88
  86. mesa/visualization/components/matplotlib.py +0 -342
  87. mesa-3.0.0b1.dist-info/RECORD +0 -114
  88. {examples → mesa/examples/advanced}/__init__.py +0 -0
  89. {examples → mesa/examples}/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +0 -0
  90. {examples/advanced → mesa/examples/advanced/epstein_civil_violence}/__init__.py +0 -0
  91. {examples/advanced/epstein_civil_violence/epstein_civil_violence → mesa/examples/advanced/pd_grid}/__init__.py +0 -0
  92. /examples/advanced/pd_grid/pd_grid/agent.py → /mesa/examples/advanced/pd_grid/agents.py +0 -0
  93. {examples → mesa/examples}/advanced/pd_grid/analysis.ipynb +0 -0
  94. {examples/advanced/pd_grid/pd_grid → mesa/examples/advanced/sugarscape_g1mt}/__init__.py +0 -0
  95. {examples/advanced/sugarscape_g1mt → mesa/examples/advanced}/sugarscape_g1mt/sugar-map.txt +0 -0
  96. {examples → mesa/examples}/advanced/wolf_sheep/Readme.md +0 -0
  97. {examples/advanced/sugarscape_g1mt/sugarscape_g1mt → mesa/examples/advanced/wolf_sheep}/__init__.py +0 -0
  98. {examples/advanced/wolf_sheep → mesa/examples/advanced}/wolf_sheep/agents.py +0 -0
  99. {examples/advanced/wolf_sheep → mesa/examples/basic}/__init__.py +0 -0
  100. {examples/advanced/wolf_sheep/wolf_sheep → mesa/examples/basic/boid_flockers}/__init__.py +0 -0
  101. {examples → mesa/examples}/basic/boid_flockers/agents.py +0 -0
  102. {examples → mesa/examples}/basic/boltzmann_wealth_model/agents.py +0 -0
  103. {examples → mesa/examples}/basic/boltzmann_wealth_model/st_app.py +0 -0
  104. {examples → mesa/examples}/basic/schelling/agents.py +0 -0
  105. {examples → mesa/examples}/basic/schelling/analysis.ipynb +0 -0
  106. /examples/basic/virus_on_network/README.md → /mesa/examples/basic/virus_on_network/Readme.md +0 -0
  107. {examples → mesa/examples}/basic/virus_on_network/agents.py +0 -0
  108. {mesa-3.0.0b1.dist-info → mesa-3.0.0rc0.dist-info}/WHEEL +0 -0
  109. {mesa-3.0.0b1.dist-info → mesa-3.0.0rc0.dist-info}/entry_points.txt +0 -0
  110. {mesa-3.0.0b1.dist-info → mesa-3.0.0rc0.dist-info}/licenses/LICENSE +0 -0
  111. {mesa-3.0.0b1.dist-info → mesa-3.0.0rc0.dist-info}/licenses/NOTICE +0 -0
mesa/__init__.py CHANGED
@@ -5,7 +5,6 @@ Core Objects: Model, and Agent.
5
5
 
6
6
  import datetime
7
7
 
8
- import mesa.examples as examples
9
8
  import mesa.experimental as experimental
10
9
  import mesa.space as space
11
10
  import mesa.time as time
@@ -22,11 +21,10 @@ __all__ = [
22
21
  "DataCollector",
23
22
  "batch_run",
24
23
  "experimental",
25
- "examples",
26
24
  ]
27
25
 
28
26
  __title__ = "mesa"
29
- __version__ = "3.0.0b1"
27
+ __version__ = "3.0.0rc0"
30
28
  __license__ = "Apache 2.0"
31
29
  _this_year = datetime.datetime.now(tz=datetime.timezone.utc).date().year
32
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.
@@ -67,7 +69,13 @@ class Agent:
67
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).
@@ -288,15 +301,17 @@ class AgentSet(MutableSet, Sequence):
288
301
 
289
302
  It's a fast, optimized version of calling shuffle() followed by do().
290
303
  """
291
- agents = list(self._agents.keys())
292
- self.random.shuffle(agents)
304
+ weakrefs = list(self._agents.keyrefs())
305
+ self.random.shuffle(weakrefs)
293
306
 
294
307
  if isinstance(method, str):
295
- for agent in agents:
296
- getattr(agent, method)(*args, **kwargs)
308
+ for ref in weakrefs:
309
+ if (agent := ref()) is not None:
310
+ getattr(agent, method)(*args, **kwargs)
297
311
  else:
298
- for agent in agents:
299
- method(agent, *args, **kwargs)
312
+ for ref in weakrefs:
313
+ if (agent := ref()) is not None:
314
+ method(agent, *args, **kwargs)
300
315
 
301
316
  return self
302
317
 
mesa/batchrunner.py CHANGED
@@ -1,4 +1,29 @@
1
- """batchrunner for running a factorial experiment design over a model."""
1
+ """batchrunner for running a factorial experiment design over a model.
2
+
3
+ To take advantage of parallel execution of experiments, `batch_run` uses
4
+ multiprocessing if ``number_processes`` is larger than 1. It is strongly advised
5
+ to only run in parallel using a normal python file (so don't try to do it in a
6
+ jupyter notebook). Moreover, best practice when using multiprocessing is to
7
+ put the code inside an ``if __name__ == '__main__':`` code black as shown below::
8
+
9
+ from mesa.batchrunner import batch_run
10
+
11
+ params = {"width": 10, "height": 10, "N": range(10, 500, 10)}
12
+
13
+ if __name__ == '__main__':
14
+ results = batch_run(
15
+ MoneyModel,
16
+ parameters=params,
17
+ iterations=5,
18
+ max_steps=100,
19
+ number_processes=None,
20
+ data_collection_period=1,
21
+ display_progress=True,
22
+ )
23
+
24
+
25
+
26
+ """
2
27
 
3
28
  import itertools
4
29
  import multiprocessing
@@ -1,37 +1,37 @@
1
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.
2
+ These examples are a collection of classic agent based models built using Mesa. These core examples are maintained by the Mesa team and are intended to demonstrate the capabilities of Mesa.
3
3
 
4
4
  More user examples and showcases can be found in the [mesa-examples](https://github.com/projectmesa/mesa-examples) repository.
5
5
 
6
6
  ## Basic Examples
7
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
8
 
9
- ### [Boltzmann Wealth Model](basic/boltzmann_wealth_model)
9
+ ### [Boltzmann Wealth Model](examples/basic/boltzmann_wealth_model)
10
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
11
 
12
- ### [Boids Flockers Model](basic/boid_flockers)
12
+ ### [Boids Flockers Model](examples/basic/boid_flockers)
13
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
14
 
15
- ### [Conway's Game of Life](basic/conways_game_of_life)
15
+ ### [Conway's Game of Life](examples/basic/conways_game_of_life)
16
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
17
 
18
- ### [Schelling Segregation Model](basic/schelling)
18
+ ### [Schelling Segregation Model](examples/basic/schelling)
19
19
  Mesa implementation of the classic [Schelling segregation](http://nifty.stanford.edu/2014/mccown-schelling-model-segregation/) model.
20
20
 
21
- ### [Virus on a Network Model](basic/virus_on_network)
21
+ ### [Virus on a Network Model](examples/basic/virus_on_network)
22
22
  This model is based on the NetLogo [Virus on a Network](https://ccl.northwestern.edu/netlogo/models/VirusonaNetwork) model.
23
23
 
24
24
  ## Advanced Examples
25
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
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.
27
+ ### [Epstein Civil Violence Model](examples/advanced/epstein_civil_violence)
28
+ Joshua Epstein's [model](https://www.pnas.org/doi/10.1073/pnas.092080199) of how a decentralized uprising can be suppressed or reach a critical mass of support.
29
29
 
30
- ### [Demographic Prisoner's Dilemma on a Grid](advanced/pd_grid)
30
+ ### [Demographic Prisoner's Dilemma on a Grid](examples/advanced/pd_grid)
31
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
32
 
33
- ### [Sugarscape Model with Traders](advanced/sugarscape_g1mt)
33
+ ### [Sugarscape Model with Traders](examples/advanced/sugarscape_g1mt)
34
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
35
 
36
- ### [Wolf-Sheep Predation Model](advanced/wolf_sheep)
36
+ ### [Wolf-Sheep Predation Model](examples/advanced/wolf_sheep)
37
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
+ ]
@@ -18,8 +18,9 @@ Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and p
18
18
 
19
19
  ## Files
20
20
 
21
- * ``EpsteinCivilViolence.py``: Core model and agent code.
22
- * ``EpsteinCivilViolenceServer.py``: Sets up the interactive visualization.
21
+ * ``model.py``: Core model code.
22
+ * ``agent.py``: Agent classes.
23
+ * ``app.py``: Sets up the interactive visualization.
23
24
  * ``Epstein Civil Violence.ipynb``: Jupyter notebook conducting some preliminary analysis of the model.
24
25
 
25
26
  ## Further Reading
@@ -1,18 +1,29 @@
1
1
  import math
2
+ from enum import Enum
2
3
 
3
4
  import mesa
4
5
 
5
6
 
7
+ class CitizenState(Enum):
8
+ ACTIVE = 1
9
+ QUIET = 2
10
+ ARRESTED = 3
11
+
12
+
6
13
  class EpsteinAgent(mesa.experimental.cell_space.CellAgent):
7
14
  def update_neighbors(self):
8
15
  """
9
16
  Look around and see who my neighbors are
10
17
  """
11
18
  self.neighborhood = self.cell.get_neighborhood(radius=self.vision)
12
-
13
19
  self.neighbors = self.neighborhood.agents
14
20
  self.empty_neighbors = [c for c in self.neighborhood if c.is_empty]
15
21
 
22
+ def move(self):
23
+ if self.model.movement and self.empty_neighbors:
24
+ new_pos = self.random.choice(self.empty_neighbors)
25
+ self.move_to(new_pos)
26
+
16
27
 
17
28
  class Citizen(EpsteinAgent):
18
29
  """
@@ -38,13 +49,7 @@ class Citizen(EpsteinAgent):
38
49
  """
39
50
 
40
51
  def __init__(
41
- self,
42
- model,
43
- hardship,
44
- regime_legitimacy,
45
- risk_aversion,
46
- threshold,
47
- vision,
52
+ self, model, regime_legitimacy, threshold, vision, arrest_prob_constant
48
53
  ):
49
54
  """
50
55
  Create a new Citizen.
@@ -62,16 +67,21 @@ class Citizen(EpsteinAgent):
62
67
  model: model instance
63
68
  """
64
69
  super().__init__(model)
65
- self.hardship = hardship
70
+ self.hardship = self.random.random()
71
+ self.risk_aversion = self.random.random()
66
72
  self.regime_legitimacy = regime_legitimacy
67
- self.risk_aversion = risk_aversion
68
73
  self.threshold = threshold
69
- self.condition = "Quiescent"
74
+ self.state = CitizenState.QUIET
70
75
  self.vision = vision
71
76
  self.jail_sentence = 0
72
77
  self.grievance = self.hardship * (1 - self.regime_legitimacy)
78
+ self.arrest_prob_constant = arrest_prob_constant
73
79
  self.arrest_probability = None
74
80
 
81
+ self.neighborhood = []
82
+ self.neighbors = []
83
+ self.empty_neighbors = []
84
+
75
85
  def step(self):
76
86
  """
77
87
  Decide whether to activate, then move if applicable.
@@ -81,32 +91,33 @@ class Citizen(EpsteinAgent):
81
91
  return # no other changes or movements if agent is in jail.
82
92
  self.update_neighbors()
83
93
  self.update_estimated_arrest_probability()
94
+
84
95
  net_risk = self.risk_aversion * self.arrest_probability
85
- if self.grievance - net_risk > self.threshold:
86
- self.condition = "Active"
96
+ if (self.grievance - net_risk) > self.threshold:
97
+ self.state = CitizenState.ACTIVE
87
98
  else:
88
- self.condition = "Quiescent"
99
+ self.state = CitizenState.QUIET
89
100
 
90
- if self.model.movement and self.empty_neighbors:
91
- new_cell = self.random.choice(self.empty_neighbors)
92
- self.move_to(new_cell)
101
+ self.move()
93
102
 
94
103
  def update_estimated_arrest_probability(self):
95
104
  """
96
105
  Based on the ratio of cops to actives in my neighborhood, estimate the
97
106
  p(Arrest | I go active).
98
107
  """
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
- ):
108
+ cops_in_vision = 0
109
+ actives_in_vision = 1 # citizen counts herself
110
+ for neighbor in self.neighbors:
111
+ if isinstance(neighbor, Cop):
112
+ cops_in_vision += 1
113
+ elif neighbor.state == CitizenState.ACTIVE:
107
114
  actives_in_vision += 1
115
+
116
+ # there is a body of literature on this equation
117
+ # the round is not in the pnas paper but without it, its impossible to replicate
118
+ # the dynamics shown there.
108
119
  self.arrest_probability = 1 - math.exp(
109
- -1 * self.model.arrest_prob_constant * (cops_in_vision / actives_in_vision)
120
+ -1 * self.arrest_prob_constant * round(cops_in_vision / actives_in_vision)
110
121
  )
111
122
 
112
123
 
@@ -122,7 +133,7 @@ class Cop(EpsteinAgent):
122
133
  able to inspect
123
134
  """
124
135
 
125
- def __init__(self, model, vision):
136
+ def __init__(self, model, vision, max_jail_term):
126
137
  """
127
138
  Create a new Cop.
128
139
  Args:
@@ -133,6 +144,7 @@ class Cop(EpsteinAgent):
133
144
  """
134
145
  super().__init__(model)
135
146
  self.vision = vision
147
+ self.max_jail_term = max_jail_term
136
148
 
137
149
  def step(self):
138
150
  """
@@ -142,17 +154,11 @@ class Cop(EpsteinAgent):
142
154
  self.update_neighbors()
143
155
  active_neighbors = []
144
156
  for agent in self.neighbors:
145
- if (
146
- isinstance(agent, Citizen)
147
- and agent.condition == "Active"
148
- and agent.jail_sentence == 0
149
- ):
157
+ if isinstance(agent, Citizen) and agent.state == CitizenState.ACTIVE:
150
158
  active_neighbors.append(agent)
151
159
  if active_neighbors:
152
160
  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)
161
+ arrestee.jail_sentence = self.random.randint(0, self.max_jail_term)
162
+ arrestee.state = CitizenState.ARRESTED
163
+
164
+ self.move()
@@ -0,0 +1,73 @@
1
+ from mesa.examples.advanced.epstein_civil_violence.agents import (
2
+ Citizen,
3
+ CitizenState,
4
+ Cop,
5
+ )
6
+ from mesa.examples.advanced.epstein_civil_violence.model import EpsteinCivilViolence
7
+ from mesa.visualization import (
8
+ Slider,
9
+ SolaraViz,
10
+ make_plot_component,
11
+ make_space_component,
12
+ )
13
+
14
+ COP_COLOR = "#000000"
15
+
16
+ agent_colors = {
17
+ CitizenState.ACTIVE: "#FE6100",
18
+ CitizenState.QUIET: "#648FFF",
19
+ CitizenState.ARRESTED: "#808080",
20
+ }
21
+
22
+
23
+ def citizen_cop_portrayal(agent):
24
+ if agent is None:
25
+ return
26
+
27
+ portrayal = {
28
+ "size": 50,
29
+ }
30
+
31
+ if isinstance(agent, Citizen):
32
+ portrayal["color"] = agent_colors[agent.state]
33
+ elif isinstance(agent, Cop):
34
+ portrayal["color"] = COP_COLOR
35
+
36
+ return portrayal
37
+
38
+
39
+ def post_process(ax):
40
+ ax.set_aspect("equal")
41
+ ax.set_xticks([])
42
+ ax.set_yticks([])
43
+ ax.get_figure().set_size_inches(10, 10)
44
+
45
+
46
+ model_params = {
47
+ "height": 40,
48
+ "width": 40,
49
+ "citizen_density": Slider("Initial Agent Density", 0.7, 0.0, 0.9, 0.1),
50
+ "cop_density": Slider("Initial Cop Density", 0.04, 0.0, 0.1, 0.01),
51
+ "citizen_vision": Slider("Citizen Vision", 7, 1, 10, 1),
52
+ "cop_vision": Slider("Cop Vision", 7, 1, 10, 1),
53
+ "legitimacy": Slider("Government Legitimacy", 0.82, 0.0, 1, 0.01),
54
+ "max_jail_term": Slider("Max Jail Term", 30, 0, 50, 1),
55
+ }
56
+
57
+ space_component = make_space_component(
58
+ citizen_cop_portrayal, post_process=post_process, draw_grid=False
59
+ )
60
+
61
+ chart_component = make_plot_component(
62
+ {state.name.lower(): agent_colors[state] for state in CitizenState}
63
+ )
64
+
65
+ epstein_model = EpsteinCivilViolence()
66
+
67
+ page = SolaraViz(
68
+ epstein_model,
69
+ components=[space_component, chart_component],
70
+ model_params=model_params,
71
+ name="Epstein Civil Violence",
72
+ )
73
+ page # noqa
@@ -0,0 +1,114 @@
1
+ import mesa
2
+ from mesa.examples.advanced.epstein_civil_violence.agents import (
3
+ Citizen,
4
+ CitizenState,
5
+ Cop,
6
+ )
7
+
8
+
9
+ class EpsteinCivilViolence(mesa.Model):
10
+ """
11
+ Model 1 from "Modeling civil violence: An agent-based computational
12
+ approach," by Joshua Epstein.
13
+ http://www.pnas.org/content/99/suppl_3/7243.full
14
+
15
+ Args:
16
+ height: grid height
17
+ width: grid width
18
+ citizen_density: approximate % of cells occupied by citizens.
19
+ cop_density: approximate % of cells occupied by cops.
20
+ citizen_vision: number of cells in each direction (N, S, E and W) that
21
+ citizen can inspect
22
+ cop_vision: number of cells in each direction (N, S, E and W) that cop
23
+ can inspect
24
+ legitimacy: (L) citizens' perception of regime legitimacy, equal
25
+ across all citizens
26
+ max_jail_term: (J_max)
27
+ active_threshold: if (grievance - (risk_aversion * arrest_probability))
28
+ > threshold, citizen rebels
29
+ arrest_prob_constant: set to ensure agents make plausible arrest
30
+ probability estimates
31
+ movement: binary, whether agents try to move at step end
32
+ max_iters: model may not have a natural stopping point, so we set a
33
+ max.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ width=40,
39
+ height=40,
40
+ citizen_density=0.7,
41
+ cop_density=0.074,
42
+ citizen_vision=7,
43
+ cop_vision=7,
44
+ legitimacy=0.8,
45
+ max_jail_term=1000,
46
+ active_threshold=0.1,
47
+ arrest_prob_constant=2.3,
48
+ movement=True,
49
+ max_iters=1000,
50
+ seed=None,
51
+ ):
52
+ super().__init__(seed=seed)
53
+ self.movement = movement
54
+ self.max_iters = max_iters
55
+
56
+ self.grid = mesa.experimental.cell_space.OrthogonalVonNeumannGrid(
57
+ (width, height), capacity=1, torus=True, random=self.random
58
+ )
59
+
60
+ model_reporters = {
61
+ "active": CitizenState.ACTIVE.name,
62
+ "quiet": CitizenState.QUIET.name,
63
+ "arrested": CitizenState.ARRESTED.name,
64
+ }
65
+ agent_reporters = {
66
+ "jail_sentence": lambda a: getattr(a, "jail_sentence", None),
67
+ "arrest_probability": lambda a: getattr(a, "arrest_probability", None),
68
+ }
69
+ self.datacollector = mesa.DataCollector(
70
+ model_reporters=model_reporters, agent_reporters=agent_reporters
71
+ )
72
+ if cop_density + citizen_density > 1:
73
+ raise ValueError("Cop density + citizen density must be less than 1")
74
+
75
+ for cell in self.grid.all_cells:
76
+ klass = self.random.choices(
77
+ [Citizen, Cop, None],
78
+ cum_weights=[citizen_density, citizen_density + cop_density, 1],
79
+ )[0]
80
+
81
+ if klass == Cop:
82
+ cop = Cop(self, vision=cop_vision, max_jail_term=max_jail_term)
83
+ cop.move_to(cell)
84
+ elif klass == Citizen:
85
+ citizen = Citizen(
86
+ self,
87
+ regime_legitimacy=legitimacy,
88
+ threshold=active_threshold,
89
+ vision=citizen_vision,
90
+ arrest_prob_constant=arrest_prob_constant,
91
+ )
92
+ citizen.move_to(cell)
93
+
94
+ self.running = True
95
+ self._update_counts()
96
+ self.datacollector.collect(self)
97
+
98
+ def step(self):
99
+ """
100
+ Advance the model by one step and collect data.
101
+ """
102
+ self.agents.shuffle_do("step")
103
+ self._update_counts()
104
+ self.datacollector.collect(self)
105
+
106
+ if self.steps > self.max_iters:
107
+ self.running = False
108
+
109
+ def _update_counts(self):
110
+ """Helper function for counting nr. of citizens in given state."""
111
+ counts = self.agents_by_type[Citizen].groupby("state").count()
112
+
113
+ for state in CitizenState:
114
+ setattr(self, state.name, counts.get(state, 0))
@@ -19,7 +19,7 @@ The Demographic Prisoner's Dilemma demonstrates how simple rules can lead to the
19
19
 
20
20
  ##### Web based model simulation
21
21
 
22
- To run the model interactively, run ``mesa runserver`` in this directory.
22
+ To run the model interactively, run ``solara run app.py`` in this directory.
23
23
 
24
24
  ##### Jupyter Notebook
25
25
 
@@ -27,8 +27,9 @@ Launch the ``Demographic Prisoner's Dilemma Activation Schedule.ipynb`` notebook
27
27
 
28
28
  ## Files
29
29
 
30
- * ``run.py`` is the entry point for the font-end simulations.
31
- * ``pd_grid/``: contains the model and agent classes; the model takes a ``activation_order`` string as an argument, which determines in which order agents are activated: Sequential, Random or Simultaneous.
30
+ * ``agents.py``: contains the agent class.
31
+ * ``model.py``: contains the model class; the model takes a ``activation_order`` string as an argument, which determines in which order agents are activated: Sequential, Random or Simultaneous.
32
+ * ``app.py``: contains the interactive visualization server.
32
33
  * ``Demographic Prisoner's Dilemma Activation Schedule.ipynb``: Jupyter Notebook for running the scheduling experiment. This runs the model three times, one for each activation type, and demonstrates how the activation regime drives the model to different outcomes.
33
34
 
34
35
  ## Further Reading
@@ -0,0 +1,54 @@
1
+ """
2
+ Solara-based visualization for the Spatial Prisoner's Dilemma Model.
3
+ """
4
+
5
+ from mesa.examples.advanced.pd_grid.model import PdGrid
6
+ from mesa.visualization import (
7
+ SolaraViz,
8
+ make_plot_component,
9
+ make_space_component,
10
+ )
11
+ from mesa.visualization.UserParam import Slider
12
+
13
+
14
+ def pd_agent_portrayal(agent):
15
+ """
16
+ Portrayal function for rendering PD agents in the visualization.
17
+ """
18
+ return {
19
+ "color": "blue" if agent.move == "C" else "red",
20
+ "marker": "s", # square marker
21
+ "size": 25,
22
+ }
23
+
24
+
25
+ # Model parameters
26
+ model_params = {
27
+ "width": Slider("Grid Width", value=50, min=10, max=100, step=1),
28
+ "height": Slider("Grid Height", value=50, min=10, max=100, step=1),
29
+ "activation_order": {
30
+ "type": "Select",
31
+ "value": "Random",
32
+ "values": PdGrid.activation_regimes,
33
+ "label": "Activation Regime",
34
+ },
35
+ }
36
+
37
+
38
+ # Create grid visualization component using Altair
39
+ grid_viz = make_space_component(agent_portrayal=pd_agent_portrayal)
40
+
41
+ # Create plot for tracking cooperating agents over time
42
+ plot_component = make_plot_component("Cooperating_Agents")
43
+
44
+ # Initialize model
45
+ initial_model = PdGrid()
46
+
47
+ # Create visualization with all components
48
+ page = SolaraViz(
49
+ model=initial_model,
50
+ components=[grid_viz, plot_component],
51
+ model_params=model_params,
52
+ name="Spatial Prisoner's Dilemma",
53
+ )
54
+ page # noqa B018
@@ -1,8 +1,7 @@
1
1
  import mesa
2
+ from mesa.examples.advanced.pd_grid.agents import PDAgent
2
3
  from mesa.experimental.cell_space import OrthogonalMooreGrid
3
4
 
4
- from .agent import PDAgent
5
-
6
5
 
7
6
  class PdGrid(mesa.Model):
8
7
  """Model class for iterated, spatial prisoner's dilemma model."""