Mesa 3.0.0a5__py3-none-any.whl → 3.0.0b1__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.
- examples/README.md +37 -0
- examples/__init__.py +0 -0
- examples/advanced/__init__.py +0 -0
- examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +116 -0
- examples/advanced/epstein_civil_violence/Readme.md +33 -0
- examples/advanced/epstein_civil_violence/epstein_civil_violence/__init__.py +0 -0
- examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py +158 -0
- examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +146 -0
- examples/advanced/epstein_civil_violence/epstein_civil_violence/portrayal.py +33 -0
- examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py +81 -0
- examples/advanced/epstein_civil_violence/requirements.txt +3 -0
- examples/advanced/epstein_civil_violence/run.py +3 -0
- examples/advanced/pd_grid/analysis.ipynb +228 -0
- examples/advanced/pd_grid/pd_grid/__init__.py +0 -0
- examples/advanced/pd_grid/pd_grid/agent.py +50 -0
- examples/advanced/pd_grid/pd_grid/model.py +72 -0
- examples/advanced/pd_grid/pd_grid/portrayal.py +19 -0
- examples/advanced/pd_grid/pd_grid/server.py +21 -0
- examples/advanced/pd_grid/readme.md +42 -0
- examples/advanced/pd_grid/requirements.txt +3 -0
- examples/advanced/pd_grid/run.py +3 -0
- examples/advanced/sugarscape_g1mt/Readme.md +87 -0
- examples/advanced/sugarscape_g1mt/app.py +61 -0
- examples/advanced/sugarscape_g1mt/requirements.txt +6 -0
- examples/advanced/sugarscape_g1mt/run.py +105 -0
- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/__init__.py +0 -0
- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +180 -0
- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py +26 -0
- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py +61 -0
- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/sugar-map.txt +50 -0
- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +321 -0
- examples/advanced/sugarscape_g1mt/tests.py +72 -0
- examples/advanced/wolf_sheep/Readme.md +57 -0
- examples/advanced/wolf_sheep/__init__.py +0 -0
- examples/advanced/wolf_sheep/requirements.txt +1 -0
- examples/advanced/wolf_sheep/run.py +3 -0
- examples/advanced/wolf_sheep/wolf_sheep/__init__.py +0 -0
- examples/advanced/wolf_sheep/wolf_sheep/agents.py +102 -0
- examples/advanced/wolf_sheep/wolf_sheep/model.py +136 -0
- examples/advanced/wolf_sheep/wolf_sheep/resources/sheep.png +0 -0
- examples/advanced/wolf_sheep/wolf_sheep/resources/wolf.png +0 -0
- examples/advanced/wolf_sheep/wolf_sheep/server.py +78 -0
- examples/basic/__init__.py +13 -0
- examples/basic/boid_flockers/Readme.md +43 -0
- examples/basic/boid_flockers/agents.py +71 -0
- examples/basic/boid_flockers/app.py +59 -0
- examples/basic/boid_flockers/model.py +70 -0
- examples/basic/boltzmann_wealth_model/Readme.md +60 -0
- examples/basic/boltzmann_wealth_model/agents.py +31 -0
- examples/basic/boltzmann_wealth_model/app.py +66 -0
- examples/basic/boltzmann_wealth_model/model.py +44 -0
- examples/basic/boltzmann_wealth_model/st_app.py +115 -0
- examples/basic/conways_game_of_life/Readme.md +35 -0
- examples/basic/conways_game_of_life/agents.py +47 -0
- examples/basic/conways_game_of_life/model.py +32 -0
- examples/basic/conways_game_of_life/portrayal.py +18 -0
- examples/basic/conways_game_of_life/requirements.txt +1 -0
- examples/basic/conways_game_of_life/server.py +11 -0
- examples/basic/conways_game_of_life/st_app.py +71 -0
- examples/basic/schelling/README.md +47 -0
- examples/basic/schelling/agents.py +26 -0
- examples/basic/schelling/analysis.ipynb +205 -0
- examples/basic/schelling/app.py +43 -0
- examples/basic/schelling/model.py +60 -0
- examples/basic/virus_on_network/README.md +61 -0
- examples/basic/virus_on_network/agents.py +69 -0
- examples/basic/virus_on_network/app.py +133 -0
- examples/basic/virus_on_network/model.py +99 -0
- mesa/__init__.py +4 -1
- mesa/agent.py +24 -43
- mesa/batchrunner.py +7 -0
- mesa/examples.py +3 -0
- mesa/experimental/__init__.py +8 -2
- mesa/experimental/cell_space/__init__.py +7 -1
- mesa/experimental/cell_space/cell.py +35 -6
- mesa/experimental/cell_space/cell_agent.py +114 -23
- mesa/experimental/cell_space/discrete_space.py +70 -3
- mesa/experimental/cell_space/grid.py +13 -0
- mesa/experimental/cell_space/network.py +3 -0
- mesa/experimental/devs/examples/wolf_sheep.py +2 -1
- mesa/model.py +71 -21
- mesa/time.py +7 -5
- mesa/visualization/components/matplotlib.py +184 -90
- mesa/visualization/solara_viz.py +25 -61
- {mesa-3.0.0a5.dist-info → mesa-3.0.0b1.dist-info}/METADATA +55 -13
- mesa-3.0.0b1.dist-info/RECORD +114 -0
- mesa-3.0.0b1.dist-info/licenses/LICENSE +202 -0
- mesa-3.0.0a5.dist-info/licenses/LICENSE → mesa-3.0.0b1.dist-info/licenses/NOTICE +2 -2
- mesa-3.0.0a5.dist-info/RECORD +0 -44
- {mesa-3.0.0a5.dist-info → mesa-3.0.0b1.dist-info}/WHEEL +0 -0
- {mesa-3.0.0a5.dist-info → mesa-3.0.0b1.dist-info}/entry_points.txt +0 -0
|
Binary file
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import mesa
|
|
2
|
+
from wolf_sheep.agents import GrassPatch, Sheep, Wolf
|
|
3
|
+
from wolf_sheep.model import WolfSheep
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def wolf_sheep_portrayal(agent):
|
|
7
|
+
if agent is None:
|
|
8
|
+
return
|
|
9
|
+
|
|
10
|
+
portrayal = {}
|
|
11
|
+
|
|
12
|
+
if type(agent) is Sheep:
|
|
13
|
+
portrayal["Shape"] = "wolf_sheep/resources/sheep.png"
|
|
14
|
+
# https://icons8.com/web-app/433/sheep
|
|
15
|
+
portrayal["scale"] = 0.9
|
|
16
|
+
portrayal["Layer"] = 1
|
|
17
|
+
|
|
18
|
+
elif type(agent) is Wolf:
|
|
19
|
+
portrayal["Shape"] = "wolf_sheep/resources/wolf.png"
|
|
20
|
+
# https://icons8.com/web-app/36821/German-Shepherd
|
|
21
|
+
portrayal["scale"] = 0.9
|
|
22
|
+
portrayal["Layer"] = 2
|
|
23
|
+
portrayal["text"] = round(agent.energy, 1)
|
|
24
|
+
portrayal["text_color"] = "White"
|
|
25
|
+
|
|
26
|
+
elif type(agent) is GrassPatch:
|
|
27
|
+
if agent.fully_grown:
|
|
28
|
+
portrayal["Color"] = ["#00FF00", "#00CC00", "#009900"]
|
|
29
|
+
else:
|
|
30
|
+
portrayal["Color"] = ["#84e184", "#adebad", "#d6f5d6"]
|
|
31
|
+
portrayal["Shape"] = "rect"
|
|
32
|
+
portrayal["Filled"] = "true"
|
|
33
|
+
portrayal["Layer"] = 0
|
|
34
|
+
portrayal["w"] = 1
|
|
35
|
+
portrayal["h"] = 1
|
|
36
|
+
|
|
37
|
+
return portrayal
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
canvas_element = mesa.visualization.CanvasGrid(wolf_sheep_portrayal, 20, 20, 500, 500)
|
|
41
|
+
chart_element = mesa.visualization.ChartModule(
|
|
42
|
+
[
|
|
43
|
+
{"Label": "Wolves", "Color": "#AA0000"},
|
|
44
|
+
{"Label": "Sheep", "Color": "#666666"},
|
|
45
|
+
{"Label": "Grass", "Color": "#00AA00"},
|
|
46
|
+
]
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
model_params = {
|
|
50
|
+
# The following line is an example to showcase StaticText.
|
|
51
|
+
"title": mesa.visualization.StaticText("Parameters:"),
|
|
52
|
+
"grass": mesa.visualization.Checkbox("Grass Enabled", True),
|
|
53
|
+
"grass_regrowth_time": mesa.visualization.Slider("Grass Regrowth Time", 20, 1, 50),
|
|
54
|
+
"initial_sheep": mesa.visualization.Slider(
|
|
55
|
+
"Initial Sheep Population", 100, 10, 300
|
|
56
|
+
),
|
|
57
|
+
"sheep_reproduce": mesa.visualization.Slider(
|
|
58
|
+
"Sheep Reproduction Rate", 0.04, 0.01, 1.0, 0.01
|
|
59
|
+
),
|
|
60
|
+
"initial_wolves": mesa.visualization.Slider("Initial Wolf Population", 50, 10, 300),
|
|
61
|
+
"wolf_reproduce": mesa.visualization.Slider(
|
|
62
|
+
"Wolf Reproduction Rate",
|
|
63
|
+
0.05,
|
|
64
|
+
0.01,
|
|
65
|
+
1.0,
|
|
66
|
+
0.01,
|
|
67
|
+
description="The rate at which wolf agents reproduce.",
|
|
68
|
+
),
|
|
69
|
+
"wolf_gain_from_food": mesa.visualization.Slider(
|
|
70
|
+
"Wolf Gain From Food Rate", 20, 1, 50
|
|
71
|
+
),
|
|
72
|
+
"sheep_gain_from_food": mesa.visualization.Slider("Sheep Gain From Food", 4, 1, 10),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
server = mesa.visualization.ModularServer(
|
|
76
|
+
WolfSheep, [canvas_element, chart_element], "Wolf Sheep Predation", model_params
|
|
77
|
+
)
|
|
78
|
+
server.port = 8521
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .boid_flockers.model import BoidFlockers
|
|
2
|
+
from .boltzmann_wealth_model.model import BoltzmannWealthModel
|
|
3
|
+
from .conways_game_of_life.model import ConwaysGameOfLife
|
|
4
|
+
from .schelling.model import Schelling
|
|
5
|
+
from .virus_on_network.model import VirusOnNetwork
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"BoidFlockers",
|
|
9
|
+
"BoltzmannWealthModel",
|
|
10
|
+
"ConwaysGameOfLife",
|
|
11
|
+
"Schelling",
|
|
12
|
+
"VirusOnNetwork",
|
|
13
|
+
]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Boids Flockers
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
An implementation of Craig Reynolds's Boids flocker model. Agents (simulated birds) try to fly towards the average position of their neighbors and in the same direction as them, while maintaining a minimum distance. This produces flocking behavior.
|
|
6
|
+
|
|
7
|
+
This model tests Mesa's continuous space feature, and uses numpy arrays to represent vectors. It also demonstrates how to create custom visualization components.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
To install the dependencies use pip and the requirements.txt in this directory. e.g.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
$ pip install -r requirements.txt
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## How to Run
|
|
18
|
+
|
|
19
|
+
* To launch the visualization interactively, run ``mesa runserver`` in this directory. e.g.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
$ mesa runserver
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
or
|
|
26
|
+
|
|
27
|
+
Directly run the file ``run.py`` in the terminal. e.g.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
$ python run.py
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
* Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run.
|
|
34
|
+
|
|
35
|
+
## Files
|
|
36
|
+
|
|
37
|
+
* [model.py](model.py): Core model file; contains the Boid Model and Boid Agent class.
|
|
38
|
+
* [app.py](app.py): Visualization code.
|
|
39
|
+
|
|
40
|
+
## Further Reading
|
|
41
|
+
|
|
42
|
+
The following link can be visited for more information on the boid flockers model:
|
|
43
|
+
https://cs.stanford.edu/people/eroberts/courses/soco/projects/2008-09/modeling-natural-systems/boids.html
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from mesa import Agent
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Boid(Agent):
|
|
7
|
+
"""A Boid-style flocker agent.
|
|
8
|
+
|
|
9
|
+
The agent follows three behaviors to flock:
|
|
10
|
+
- Cohesion: steering towards neighboring agents.
|
|
11
|
+
- Separation: avoiding getting too close to any other agent.
|
|
12
|
+
- Alignment: try to fly in the same direction as the neighbors.
|
|
13
|
+
|
|
14
|
+
Boids have a vision that defines the radius in which they look for their
|
|
15
|
+
neighbors to flock with. Their speed (a scalar) and direction (a vector)
|
|
16
|
+
define their movement. Separation is their desired minimum distance from
|
|
17
|
+
any other Boid.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
model,
|
|
23
|
+
speed,
|
|
24
|
+
direction,
|
|
25
|
+
vision,
|
|
26
|
+
separation,
|
|
27
|
+
cohere=0.03,
|
|
28
|
+
separate=0.015,
|
|
29
|
+
match=0.05,
|
|
30
|
+
):
|
|
31
|
+
"""Create a new Boid flocker agent.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
speed: Distance to move per step.
|
|
35
|
+
direction: numpy vector for the Boid's direction of movement.
|
|
36
|
+
vision: Radius to look around for nearby Boids.
|
|
37
|
+
separation: Minimum distance to maintain from other Boids.
|
|
38
|
+
cohere: the relative importance of matching neighbors' positions
|
|
39
|
+
separate: the relative importance of avoiding close neighbors
|
|
40
|
+
match: the relative importance of matching neighbors' headings
|
|
41
|
+
"""
|
|
42
|
+
super().__init__(model)
|
|
43
|
+
self.speed = speed
|
|
44
|
+
self.direction = direction
|
|
45
|
+
self.vision = vision
|
|
46
|
+
self.separation = separation
|
|
47
|
+
self.cohere_factor = cohere
|
|
48
|
+
self.separate_factor = separate
|
|
49
|
+
self.match_factor = match
|
|
50
|
+
self.neighbors = None
|
|
51
|
+
|
|
52
|
+
def step(self):
|
|
53
|
+
"""Get the Boid's neighbors, compute the new vector, and move accordingly."""
|
|
54
|
+
self.neighbors = self.model.space.get_neighbors(self.pos, self.vision, False)
|
|
55
|
+
n = 0
|
|
56
|
+
match_vector, separation_vector, cohere = np.zeros((3, 2))
|
|
57
|
+
for neighbor in self.neighbors:
|
|
58
|
+
n += 1
|
|
59
|
+
heading = self.model.space.get_heading(self.pos, neighbor.pos)
|
|
60
|
+
cohere += heading
|
|
61
|
+
if self.model.space.get_distance(self.pos, neighbor.pos) < self.separation:
|
|
62
|
+
separation_vector -= heading
|
|
63
|
+
match_vector += neighbor.direction
|
|
64
|
+
n = max(n, 1)
|
|
65
|
+
cohere = cohere * self.cohere_factor
|
|
66
|
+
separation_vector = separation_vector * self.separate_factor
|
|
67
|
+
match_vector = match_vector * self.match_factor
|
|
68
|
+
self.direction += (cohere + separation_vector + match_vector) / n
|
|
69
|
+
self.direction /= np.linalg.norm(self.direction)
|
|
70
|
+
new_pos = self.pos + self.direction * self.speed
|
|
71
|
+
self.model.space.move_agent(self, new_pos)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from mesa.visualization import Slider, SolaraViz, make_space_matplotlib
|
|
2
|
+
|
|
3
|
+
from .model import BoidFlockers
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def boid_draw(agent):
|
|
7
|
+
if not agent.neighbors: # Only for the first Frame
|
|
8
|
+
neighbors = len(agent.model.space.get_neighbors(agent.pos, agent.vision, False))
|
|
9
|
+
else:
|
|
10
|
+
neighbors = len(agent.neighbors)
|
|
11
|
+
|
|
12
|
+
if neighbors <= 1:
|
|
13
|
+
return {"color": "red", "size": 20}
|
|
14
|
+
elif neighbors >= 2:
|
|
15
|
+
return {"color": "green", "size": 20}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
model_params = {
|
|
19
|
+
"population": Slider(
|
|
20
|
+
label="Number of boids",
|
|
21
|
+
value=100,
|
|
22
|
+
min=10,
|
|
23
|
+
max=200,
|
|
24
|
+
step=10,
|
|
25
|
+
),
|
|
26
|
+
"width": 100,
|
|
27
|
+
"height": 100,
|
|
28
|
+
"speed": Slider(
|
|
29
|
+
label="Speed of Boids",
|
|
30
|
+
value=5,
|
|
31
|
+
min=1,
|
|
32
|
+
max=20,
|
|
33
|
+
step=1,
|
|
34
|
+
),
|
|
35
|
+
"vision": Slider(
|
|
36
|
+
label="Vision of Bird (radius)",
|
|
37
|
+
value=10,
|
|
38
|
+
min=1,
|
|
39
|
+
max=50,
|
|
40
|
+
step=1,
|
|
41
|
+
),
|
|
42
|
+
"separation": Slider(
|
|
43
|
+
label="Minimum Separation",
|
|
44
|
+
value=2,
|
|
45
|
+
min=1,
|
|
46
|
+
max=20,
|
|
47
|
+
step=1,
|
|
48
|
+
),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
model = BoidFlockers()
|
|
52
|
+
|
|
53
|
+
page = SolaraViz(
|
|
54
|
+
model,
|
|
55
|
+
[make_space_matplotlib(agent_portrayal=boid_draw)],
|
|
56
|
+
model_params=model_params,
|
|
57
|
+
name="Boid Flocking Model",
|
|
58
|
+
)
|
|
59
|
+
page # noqa
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Flockers.
|
|
2
|
+
=============================================================
|
|
3
|
+
A Mesa implementation of Craig Reynolds's Boids flocker model.
|
|
4
|
+
Uses numpy arrays to represent vectors.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
import mesa
|
|
10
|
+
|
|
11
|
+
from .agents import Boid
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BoidFlockers(mesa.Model):
|
|
15
|
+
"""Flocker model class. Handles agent creation, placement and scheduling."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
seed=None,
|
|
20
|
+
population=100,
|
|
21
|
+
width=100,
|
|
22
|
+
height=100,
|
|
23
|
+
vision=10,
|
|
24
|
+
speed=1,
|
|
25
|
+
separation=1,
|
|
26
|
+
cohere=0.03,
|
|
27
|
+
separate=0.015,
|
|
28
|
+
match=0.05,
|
|
29
|
+
):
|
|
30
|
+
"""Create a new Flockers model.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
population: Number of Boids
|
|
34
|
+
width, height: Size of the space.
|
|
35
|
+
speed: How fast should the Boids move.
|
|
36
|
+
vision: How far around should each Boid look for its neighbors
|
|
37
|
+
separation: What's the minimum distance each Boid will attempt to
|
|
38
|
+
keep from any other
|
|
39
|
+
cohere, separate, match: factors for the relative importance of
|
|
40
|
+
the three drives.
|
|
41
|
+
"""
|
|
42
|
+
super().__init__(seed=seed)
|
|
43
|
+
self.population = population
|
|
44
|
+
self.vision = vision
|
|
45
|
+
self.speed = speed
|
|
46
|
+
self.separation = separation
|
|
47
|
+
|
|
48
|
+
self.space = mesa.space.ContinuousSpace(width, height, True)
|
|
49
|
+
self.factors = {"cohere": cohere, "separate": separate, "match": match}
|
|
50
|
+
self.make_agents()
|
|
51
|
+
|
|
52
|
+
def make_agents(self):
|
|
53
|
+
"""Create self.population agents, with random positions and starting headings."""
|
|
54
|
+
for _ in range(self.population):
|
|
55
|
+
x = self.random.random() * self.space.x_max
|
|
56
|
+
y = self.random.random() * self.space.y_max
|
|
57
|
+
pos = np.array((x, y))
|
|
58
|
+
direction = np.random.random(2) * 2 - 1
|
|
59
|
+
boid = Boid(
|
|
60
|
+
model=self,
|
|
61
|
+
speed=self.speed,
|
|
62
|
+
direction=direction,
|
|
63
|
+
vision=self.vision,
|
|
64
|
+
separation=self.separation,
|
|
65
|
+
**self.factors,
|
|
66
|
+
)
|
|
67
|
+
self.space.place_agent(boid, pos)
|
|
68
|
+
|
|
69
|
+
def step(self):
|
|
70
|
+
self.agents.shuffle_do("step")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Boltzmann Wealth Model (Tutorial)
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
A simple model of agents exchanging wealth. All agents start with the same amount of money. Every step, each agent with one unit of money or more gives one unit of wealth to another random agent. This is the model described in the [Intro Tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html), with the completed code.
|
|
6
|
+
|
|
7
|
+
If you want to go over the step-by-step tutorial, please go and run the [Jupyter Notebook](https://github.com/projectmesa/mesa/blob/main/docs/tutorials/intro_tutorial.ipynb). The code here runs the finalized code in the last cells directly.
|
|
8
|
+
|
|
9
|
+
As the model runs, the distribution of wealth among agents goes from being perfectly uniform (all agents have the same starting wealth), to highly skewed -- a small number have high wealth, more have none at all.
|
|
10
|
+
|
|
11
|
+
## How to Run
|
|
12
|
+
|
|
13
|
+
To follow the tutorial example, launch the Jupyter Notebook and run the code in ``Introduction to Mesa Tutorial Code.ipynb`` which you can find in the main mesa repo [here](https://github.com/projectmesa/mesa/blob/main/docs/tutorials/intro_tutorial.ipynb)
|
|
14
|
+
|
|
15
|
+
Make sure to install the requirements first:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
$ pip install -r requirements.txt
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
To launch the interactive server, as described in the [last section of the tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html#adding-visualization), run:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
$ solara run app.py
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If your browser doesn't open automatically, point it to [http://127.0.0.1:8765/](http://127.0.0.1:8765/). When the visualization loads, click on the Play button.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## Files
|
|
31
|
+
|
|
32
|
+
* ``model.py``: Final version of the model.
|
|
33
|
+
* ``app.py``: Code for the interactive visualization.
|
|
34
|
+
|
|
35
|
+
## Optional
|
|
36
|
+
|
|
37
|
+
An optional visualization is also provided using Streamlit, which is another popular Python library for creating interactive web applications.
|
|
38
|
+
|
|
39
|
+
To run the Streamlit app, you will need to install the `streamlit` and `altair` libraries:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
$ pip install streamlit altair
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Then, you can run the Streamlit app using the following command:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
$ streamlit run st_app.py
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Further Reading
|
|
52
|
+
|
|
53
|
+
The full tutorial describing how the model is built can be found at:
|
|
54
|
+
https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html
|
|
55
|
+
|
|
56
|
+
This model is drawn from econophysics and presents a statistical mechanics approach to wealth distribution. Some examples of further reading on the topic can be found at:
|
|
57
|
+
|
|
58
|
+
[Milakovic, M. A Statistical Equilibrium Model of Wealth Distribution. February, 2001.](https://editorialexpress.com/cgi-bin/conference/download.cgi?db_name=SCE2001&paper_id=214)
|
|
59
|
+
|
|
60
|
+
[Dragulescu, A and Yakovenko, V. Statistical Mechanics of Money, Income, and Wealth: A Short Survey. November, 2002](http://arxiv.org/pdf/cond-mat/0211175v1.pdf)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from mesa import Agent
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MoneyAgent(Agent):
|
|
5
|
+
"""An agent with fixed initial wealth."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, model):
|
|
8
|
+
super().__init__(model)
|
|
9
|
+
self.wealth = 1
|
|
10
|
+
|
|
11
|
+
def move(self):
|
|
12
|
+
possible_steps = self.model.grid.get_neighborhood(
|
|
13
|
+
self.pos, moore=True, include_center=False
|
|
14
|
+
)
|
|
15
|
+
new_position = self.random.choice(possible_steps)
|
|
16
|
+
self.model.grid.move_agent(self, new_position)
|
|
17
|
+
|
|
18
|
+
def give_money(self):
|
|
19
|
+
cellmates = self.model.grid.get_cell_list_contents([self.pos])
|
|
20
|
+
cellmates.pop(
|
|
21
|
+
cellmates.index(self)
|
|
22
|
+
) # Ensure agent is not giving money to itself
|
|
23
|
+
if len(cellmates) > 0:
|
|
24
|
+
other = self.random.choice(cellmates)
|
|
25
|
+
other.wealth += 1
|
|
26
|
+
self.wealth -= 1
|
|
27
|
+
|
|
28
|
+
def step(self):
|
|
29
|
+
self.move()
|
|
30
|
+
if self.wealth > 0:
|
|
31
|
+
self.give_money()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from mesa.visualization import (
|
|
2
|
+
SolaraViz,
|
|
3
|
+
make_plot_measure,
|
|
4
|
+
make_space_matplotlib,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
from .model import BoltzmannWealthModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def agent_portrayal(agent):
|
|
11
|
+
size = 10
|
|
12
|
+
color = "tab:red"
|
|
13
|
+
if agent.wealth > 0:
|
|
14
|
+
size = 50
|
|
15
|
+
color = "tab:blue"
|
|
16
|
+
return {"size": size, "color": color}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
model_params = {
|
|
20
|
+
"n": {
|
|
21
|
+
"type": "SliderInt",
|
|
22
|
+
"value": 50,
|
|
23
|
+
"label": "Number of agents:",
|
|
24
|
+
"min": 10,
|
|
25
|
+
"max": 100,
|
|
26
|
+
"step": 1,
|
|
27
|
+
},
|
|
28
|
+
"width": 10,
|
|
29
|
+
"height": 10,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Create initial model instance
|
|
33
|
+
model1 = BoltzmannWealthModel(50, 10, 10)
|
|
34
|
+
|
|
35
|
+
# Create visualization elements. The visualization elements are solara components
|
|
36
|
+
# that receive the model instance as a "prop" and display it in a certain way.
|
|
37
|
+
# Under the hood these are just classes that receive the model instance.
|
|
38
|
+
# You can also author your own visualization elements, which can also be functions
|
|
39
|
+
# that receive the model instance and return a valid solara component.
|
|
40
|
+
SpaceGraph = make_space_matplotlib(agent_portrayal)
|
|
41
|
+
GiniPlot = make_plot_measure("Gini")
|
|
42
|
+
|
|
43
|
+
# Create the SolaraViz page. This will automatically create a server and display the
|
|
44
|
+
# visualization elements in a web browser.
|
|
45
|
+
# Display it using the following command in the example directory:
|
|
46
|
+
# solara run app.py
|
|
47
|
+
# It will automatically update and display any changes made to this file
|
|
48
|
+
page = SolaraViz(
|
|
49
|
+
model1,
|
|
50
|
+
components=[SpaceGraph, GiniPlot],
|
|
51
|
+
model_params=model_params,
|
|
52
|
+
name="Boltzmann Wealth Model",
|
|
53
|
+
)
|
|
54
|
+
page # noqa
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# In a notebook environment, we can also display the visualization elements directly
|
|
58
|
+
# SpaceGraph(model1)
|
|
59
|
+
# GiniPlot(model1)
|
|
60
|
+
|
|
61
|
+
# The plots will be static. If you want to pick up model steps,
|
|
62
|
+
# you have to make the model reactive first
|
|
63
|
+
# reactive_model = solara.reactive(model1)
|
|
64
|
+
# SpaceGraph(reactive_model)
|
|
65
|
+
# In a different notebook block:
|
|
66
|
+
# reactive_model.value.step()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import mesa
|
|
2
|
+
|
|
3
|
+
from .agents import MoneyAgent
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BoltzmannWealthModel(mesa.Model):
|
|
7
|
+
"""A simple model of an economy where agents exchange currency at random.
|
|
8
|
+
|
|
9
|
+
All the agents begin with one unit of currency, and each time step can give
|
|
10
|
+
a unit of currency to another agent. Note how, over time, this produces a
|
|
11
|
+
highly skewed distribution of wealth.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, n=100, width=10, height=10):
|
|
15
|
+
super().__init__()
|
|
16
|
+
self.num_agents = n
|
|
17
|
+
self.grid = mesa.space.MultiGrid(width, height, True)
|
|
18
|
+
|
|
19
|
+
self.datacollector = mesa.DataCollector(
|
|
20
|
+
model_reporters={"Gini": self.compute_gini},
|
|
21
|
+
agent_reporters={"Wealth": "wealth"},
|
|
22
|
+
)
|
|
23
|
+
# Create agents
|
|
24
|
+
for _ in range(self.num_agents):
|
|
25
|
+
a = MoneyAgent(self)
|
|
26
|
+
|
|
27
|
+
# Add the agent to a random grid cell
|
|
28
|
+
x = self.random.randrange(self.grid.width)
|
|
29
|
+
y = self.random.randrange(self.grid.height)
|
|
30
|
+
self.grid.place_agent(a, (x, y))
|
|
31
|
+
|
|
32
|
+
self.running = True
|
|
33
|
+
self.datacollector.collect(self)
|
|
34
|
+
|
|
35
|
+
def step(self):
|
|
36
|
+
self.agents.shuffle_do("step")
|
|
37
|
+
self.datacollector.collect(self)
|
|
38
|
+
|
|
39
|
+
def compute_gini(self):
|
|
40
|
+
agent_wealths = [agent.wealth for agent in self.agents]
|
|
41
|
+
x = sorted(agent_wealths)
|
|
42
|
+
n = self.num_agents
|
|
43
|
+
b = sum(xi * (n - i) for i, xi in enumerate(x)) / (n * sum(x))
|
|
44
|
+
return 1 + (1 / n) - 2 * b
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Run with streamlit run st_app.py
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import altair as alt
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import streamlit as st
|
|
8
|
+
from model import BoltzmannWealthModel
|
|
9
|
+
|
|
10
|
+
model = st.title("Boltzman Wealth Model")
|
|
11
|
+
num_agents = st.slider(
|
|
12
|
+
"Choose how many agents to include in the model",
|
|
13
|
+
min_value=1,
|
|
14
|
+
max_value=100,
|
|
15
|
+
value=50,
|
|
16
|
+
)
|
|
17
|
+
num_ticks = st.slider(
|
|
18
|
+
"Select number of Simulation Runs", min_value=1, max_value=100, value=50
|
|
19
|
+
)
|
|
20
|
+
height = st.slider("Select Grid Height", min_value=10, max_value=100, step=10, value=15)
|
|
21
|
+
width = st.slider("Select Grid Width", min_value=10, max_value=100, step=10, value=20)
|
|
22
|
+
model = BoltzmannWealthModel(num_agents, height, width)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
status_text = st.empty()
|
|
26
|
+
run = st.button("Run Simulation")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if run:
|
|
30
|
+
tick = time.time()
|
|
31
|
+
step = 0
|
|
32
|
+
# init grid
|
|
33
|
+
df_grid = pd.DataFrame()
|
|
34
|
+
df_gini = pd.DataFrame({"step": [0], "gini": [-1]})
|
|
35
|
+
for x in range(width):
|
|
36
|
+
for y in range(height):
|
|
37
|
+
df_grid = pd.concat(
|
|
38
|
+
[df_grid, pd.DataFrame({"x": [x], "y": [y], "agent_count": 0})],
|
|
39
|
+
ignore_index=True,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
heatmap = (
|
|
43
|
+
alt.Chart(df_grid)
|
|
44
|
+
.mark_point(size=100)
|
|
45
|
+
.encode(x="x", y="y", color=alt.Color("agent_count"))
|
|
46
|
+
.interactive()
|
|
47
|
+
.properties(width=800, height=600)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
line = (
|
|
51
|
+
alt.Chart(df_gini)
|
|
52
|
+
.mark_line(point=True)
|
|
53
|
+
.encode(x="step", y="gini")
|
|
54
|
+
.properties(width=800, height=600)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# init progress bar
|
|
58
|
+
my_bar = st.progress(0, text="Simulation Progress") # progress
|
|
59
|
+
placeholder = st.empty()
|
|
60
|
+
st.subheader("Agent Grid")
|
|
61
|
+
chart = st.altair_chart(heatmap)
|
|
62
|
+
st.subheader("Gini Values")
|
|
63
|
+
line_chart = st.altair_chart(line)
|
|
64
|
+
|
|
65
|
+
color_scale = alt.Scale(
|
|
66
|
+
domain=[0, 1, 2, 3, 4], range=["red", "cyan", "white", "white", "blue"]
|
|
67
|
+
)
|
|
68
|
+
for i in range(num_ticks):
|
|
69
|
+
model.step()
|
|
70
|
+
my_bar.progress((i / num_ticks), text="Simulation progress")
|
|
71
|
+
placeholder.text("Step = %d" % i)
|
|
72
|
+
for cell in model.grid.coord_iter():
|
|
73
|
+
cell_content, (x, y) = cell
|
|
74
|
+
agent_count = len(cell_content)
|
|
75
|
+
selected_row = df_grid[(df_grid["x"] == x) & (df_grid["y"] == y)]
|
|
76
|
+
df_grid.loc[selected_row.index, "agent_count"] = (
|
|
77
|
+
agent_count # random.choice([1,2])
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
df_gini = pd.concat(
|
|
81
|
+
[
|
|
82
|
+
df_gini,
|
|
83
|
+
pd.DataFrame(
|
|
84
|
+
{"step": [i], "gini": [model.datacollector.model_vars["Gini"][i]]}
|
|
85
|
+
),
|
|
86
|
+
]
|
|
87
|
+
)
|
|
88
|
+
# st.table(df_grid)
|
|
89
|
+
heatmap = (
|
|
90
|
+
alt.Chart(df_grid)
|
|
91
|
+
.mark_circle(size=100)
|
|
92
|
+
.encode(x="x", y="y", color=alt.Color("agent_count", scale=color_scale))
|
|
93
|
+
.interactive()
|
|
94
|
+
.properties(width=800, height=600)
|
|
95
|
+
)
|
|
96
|
+
chart.altair_chart(heatmap)
|
|
97
|
+
|
|
98
|
+
line = (
|
|
99
|
+
alt.Chart(df_gini)
|
|
100
|
+
.mark_line(point=True)
|
|
101
|
+
.encode(x="step", y="gini")
|
|
102
|
+
.properties(width=800, height=600)
|
|
103
|
+
)
|
|
104
|
+
line_chart.altair_chart(line)
|
|
105
|
+
|
|
106
|
+
time.sleep(0.01)
|
|
107
|
+
|
|
108
|
+
tock = time.time()
|
|
109
|
+
st.success(f"Simulation completed in {tock - tick:.2f} secs")
|
|
110
|
+
|
|
111
|
+
# st.subheader('Agent Grid')
|
|
112
|
+
# fig = px.imshow(agent_counts,labels={'color':'Agent Count'})
|
|
113
|
+
# st.plotly_chart(fig)
|
|
114
|
+
# st.subheader('Gini value over sim ticks (Plotly)')
|
|
115
|
+
# chart = st.line_chart(model.datacollector.model_vars['Gini'])
|