Mesa 3.1.5__py3-none-any.whl → 3.2.0__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.
- mesa/__init__.py +3 -1
- mesa/agent.py +26 -9
- mesa/discrete_space/__init__.py +50 -0
- mesa/{experimental/cell_space → discrete_space}/cell.py +29 -10
- mesa/{experimental/cell_space → discrete_space}/cell_agent.py +1 -1
- mesa/{experimental/cell_space → discrete_space}/cell_collection.py +3 -3
- mesa/{experimental/cell_space → discrete_space}/discrete_space.py +65 -3
- mesa/{experimental/cell_space → discrete_space}/grid.py +2 -2
- mesa/{experimental/cell_space → discrete_space}/network.py +22 -2
- mesa/{experimental/cell_space → discrete_space}/property_layer.py +1 -10
- mesa/{experimental/cell_space → discrete_space}/voronoi.py +2 -2
- mesa/examples/README.md +1 -1
- mesa/examples/__init__.py +2 -0
- mesa/examples/advanced/alliance_formation/Readme.md +50 -0
- mesa/examples/advanced/alliance_formation/__init__ .py +0 -0
- mesa/examples/advanced/alliance_formation/agents.py +20 -0
- mesa/examples/advanced/alliance_formation/app.py +71 -0
- mesa/examples/advanced/alliance_formation/model.py +184 -0
- mesa/examples/advanced/epstein_civil_violence/agents.py +1 -1
- mesa/examples/advanced/epstein_civil_violence/model.py +1 -1
- mesa/examples/advanced/pd_grid/Readme.md +4 -6
- mesa/examples/advanced/pd_grid/agents.py +1 -1
- mesa/examples/advanced/pd_grid/model.py +1 -1
- mesa/examples/advanced/sugarscape_g1mt/Readme.md +4 -5
- mesa/examples/advanced/sugarscape_g1mt/agents.py +1 -1
- mesa/examples/advanced/sugarscape_g1mt/model.py +2 -2
- mesa/examples/advanced/wolf_sheep/Readme.md +2 -17
- mesa/examples/advanced/wolf_sheep/agents.py +1 -1
- mesa/examples/advanced/wolf_sheep/app.py +2 -1
- mesa/examples/advanced/wolf_sheep/model.py +1 -1
- mesa/examples/basic/boid_flockers/Readme.md +6 -1
- mesa/examples/basic/boid_flockers/agents.py +1 -0
- mesa/examples/basic/boid_flockers/app.py +17 -2
- mesa/examples/basic/boid_flockers/model.py +12 -0
- mesa/examples/basic/boltzmann_wealth_model/Readme.md +2 -12
- mesa/examples/basic/boltzmann_wealth_model/agents.py +6 -11
- mesa/examples/basic/boltzmann_wealth_model/app.py +2 -2
- mesa/examples/basic/boltzmann_wealth_model/model.py +7 -11
- mesa/examples/basic/conways_game_of_life/Readme.md +1 -9
- mesa/examples/basic/conways_game_of_life/agents.py +13 -5
- mesa/examples/basic/conways_game_of_life/model.py +10 -7
- mesa/examples/basic/schelling/Readme.md +0 -8
- mesa/examples/basic/schelling/agents.py +13 -8
- mesa/examples/basic/schelling/model.py +6 -9
- mesa/examples/basic/virus_on_network/Readme.md +0 -4
- mesa/examples/basic/virus_on_network/agents.py +13 -17
- mesa/examples/basic/virus_on_network/model.py +20 -24
- mesa/experimental/__init__.py +2 -2
- mesa/experimental/cell_space/__init__.py +18 -8
- mesa/experimental/meta_agents/__init__.py +25 -0
- mesa/experimental/meta_agents/meta_agent.py +387 -0
- mesa/model.py +3 -3
- mesa/space.py +1 -12
- mesa/visualization/__init__.py +2 -0
- mesa/visualization/command_console.py +482 -0
- mesa/visualization/components/altair_components.py +276 -16
- mesa/visualization/mpl_space_drawing.py +17 -9
- mesa/visualization/solara_viz.py +150 -21
- {mesa-3.1.5.dist-info → mesa-3.2.0.dist-info}/METADATA +12 -8
- mesa-3.2.0.dist-info/RECORD +105 -0
- mesa-3.1.5.dist-info/RECORD +0 -96
- {mesa-3.1.5.dist-info → mesa-3.2.0.dist-info}/WHEEL +0 -0
- {mesa-3.1.5.dist-info → mesa-3.2.0.dist-info}/licenses/LICENSE +0 -0
- {mesa-3.1.5.dist-info → mesa-3.2.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
import mesa
|
|
5
|
+
from mesa import Agent
|
|
6
|
+
from mesa.examples.advanced.alliance_formation.agents import AllianceAgent
|
|
7
|
+
from mesa.experimental.meta_agents.meta_agent import (
|
|
8
|
+
create_meta_agent,
|
|
9
|
+
find_combinations,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MultiLevelAllianceModel(mesa.Model):
|
|
14
|
+
"""
|
|
15
|
+
Model for simulating multi-level alliances among agents.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, n=50, mean=0.5, std_dev=0.1, seed=42):
|
|
19
|
+
"""
|
|
20
|
+
Initialize the model.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
n (int): Number of agents.
|
|
24
|
+
mean (float): Mean value for normal distribution.
|
|
25
|
+
std_dev (float): Standard deviation for normal distribution.
|
|
26
|
+
seed (int): Random seed.
|
|
27
|
+
"""
|
|
28
|
+
super().__init__(seed=seed)
|
|
29
|
+
self.population = n
|
|
30
|
+
self.network = nx.Graph() # Initialize the network
|
|
31
|
+
self.datacollector = mesa.DataCollector(model_reporters={"Network": "network"})
|
|
32
|
+
|
|
33
|
+
# Create Agents
|
|
34
|
+
power = self.rng.normal(mean, std_dev, n)
|
|
35
|
+
power = np.clip(power, 0, 1)
|
|
36
|
+
position = self.rng.normal(mean, std_dev, n)
|
|
37
|
+
position = np.clip(position, 0, 1)
|
|
38
|
+
AllianceAgent.create_agents(self, n, power, position)
|
|
39
|
+
agent_ids = [
|
|
40
|
+
(agent.unique_id, {"size": 300, "level": 0}) for agent in self.agents
|
|
41
|
+
]
|
|
42
|
+
self.network.add_nodes_from(agent_ids)
|
|
43
|
+
|
|
44
|
+
def add_link(self, meta_agent, agents):
|
|
45
|
+
"""
|
|
46
|
+
Add links between a meta agent and its constituent agents in the network.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
meta_agent (MetaAgent): The meta agent.
|
|
50
|
+
agents (list): List of agents.
|
|
51
|
+
"""
|
|
52
|
+
for agent in agents:
|
|
53
|
+
self.network.add_edge(meta_agent.unique_id, agent.unique_id)
|
|
54
|
+
|
|
55
|
+
def calculate_shapley_value(self, agents):
|
|
56
|
+
"""
|
|
57
|
+
Calculate the Shapley value of the two agents.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
agents (list): List of agents.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
tuple: Potential utility, new position, and level.
|
|
64
|
+
"""
|
|
65
|
+
positions = agents.get("position")
|
|
66
|
+
new_position = 1 - (max(positions) - min(positions))
|
|
67
|
+
potential_utility = agents.agg("power", sum) * 1.2 * new_position
|
|
68
|
+
|
|
69
|
+
value_0 = 0.5 * agents[0].power + 0.5 * (potential_utility - agents[1].power)
|
|
70
|
+
value_1 = 0.5 * agents[1].power + 0.5 * (potential_utility - agents[0].power)
|
|
71
|
+
|
|
72
|
+
if value_0 > agents[0].power and value_1 > agents[1].power:
|
|
73
|
+
if agents[0].level > agents[1].level:
|
|
74
|
+
level = agents[0].level
|
|
75
|
+
elif agents[0].level == agents[1].level:
|
|
76
|
+
level = agents[0].level + 1
|
|
77
|
+
else:
|
|
78
|
+
level = agents[1].level
|
|
79
|
+
|
|
80
|
+
return potential_utility, new_position, level
|
|
81
|
+
|
|
82
|
+
def only_best_combination(self, combinations):
|
|
83
|
+
"""
|
|
84
|
+
Filter to keep only the best combination for each agent.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
combinations (list): List of combinations.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
dict: Unique combinations.
|
|
91
|
+
"""
|
|
92
|
+
best = {}
|
|
93
|
+
# Determine best option for EACH agent
|
|
94
|
+
for group, value in combinations:
|
|
95
|
+
agent_ids = sorted(group.get("unique_id")) # by default is bilateral
|
|
96
|
+
# Deal with all possibilities
|
|
97
|
+
if (
|
|
98
|
+
agent_ids[0] not in best and agent_ids[1] not in best
|
|
99
|
+
): # if neither in add both
|
|
100
|
+
best[agent_ids[0]] = [group, value, agent_ids]
|
|
101
|
+
best[agent_ids[1]] = [group, value, agent_ids]
|
|
102
|
+
elif (
|
|
103
|
+
agent_ids[0] in best and agent_ids[1] in best
|
|
104
|
+
): # if both in, see if both would be trading up
|
|
105
|
+
if (
|
|
106
|
+
value[0] > best[agent_ids[0]][1][0]
|
|
107
|
+
and value[0] > best[agent_ids[1]][1][0]
|
|
108
|
+
):
|
|
109
|
+
# Remove the old alliances
|
|
110
|
+
del best[best[agent_ids[0]][2][1]]
|
|
111
|
+
del best[best[agent_ids[1]][2][0]]
|
|
112
|
+
# Add the new alliance
|
|
113
|
+
best[agent_ids[0]] = [group, value, agent_ids]
|
|
114
|
+
best[agent_ids[1]] = [group, value, agent_ids]
|
|
115
|
+
elif (
|
|
116
|
+
agent_ids[0] in best
|
|
117
|
+
): # if only agent_ids[0] in, see if it would be trading up
|
|
118
|
+
if value[0] > best[agent_ids[0]][1][0]:
|
|
119
|
+
# Remove the old alliance for agent_ids[0]
|
|
120
|
+
del best[best[agent_ids[0]][2][1]]
|
|
121
|
+
# Add the new alliance
|
|
122
|
+
best[agent_ids[0]] = [group, value, agent_ids]
|
|
123
|
+
best[agent_ids[1]] = [group, value, agent_ids]
|
|
124
|
+
elif (
|
|
125
|
+
agent_ids[1] in best
|
|
126
|
+
): # if only agent_ids[1] in, see if it would be trading up
|
|
127
|
+
if value[0] > best[agent_ids[1]][1][0]:
|
|
128
|
+
# Remove the old alliance for agent_ids[1]
|
|
129
|
+
del best[best[agent_ids[1]][2][0]]
|
|
130
|
+
# Add the new alliance
|
|
131
|
+
best[agent_ids[0]] = [group, value, agent_ids]
|
|
132
|
+
best[agent_ids[1]] = [group, value, agent_ids]
|
|
133
|
+
|
|
134
|
+
# Create a unique dictionary of the best combinations
|
|
135
|
+
unique_combinations = {}
|
|
136
|
+
for group, value, agents_nums in best.values():
|
|
137
|
+
unique_combinations[tuple(agents_nums)] = [group, value]
|
|
138
|
+
|
|
139
|
+
return unique_combinations.values()
|
|
140
|
+
|
|
141
|
+
def step(self):
|
|
142
|
+
"""
|
|
143
|
+
Execute one step of the model.
|
|
144
|
+
"""
|
|
145
|
+
# Get all other agents of the same type
|
|
146
|
+
agent_types = list(self.agents_by_type.keys())
|
|
147
|
+
|
|
148
|
+
for agent_type in agent_types:
|
|
149
|
+
similar_agents = self.agents_by_type[agent_type]
|
|
150
|
+
|
|
151
|
+
# Find the best combinations using find_combinations
|
|
152
|
+
if (
|
|
153
|
+
len(similar_agents) > 1
|
|
154
|
+
): # only form alliances if there are more than 1 agent
|
|
155
|
+
combinations = find_combinations(
|
|
156
|
+
self,
|
|
157
|
+
similar_agents,
|
|
158
|
+
size=2,
|
|
159
|
+
evaluation_func=self.calculate_shapley_value,
|
|
160
|
+
filter_func=self.only_best_combination,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
for alliance, attributes in combinations:
|
|
164
|
+
class_name = f"MetaAgentLevel{attributes[2]}"
|
|
165
|
+
meta = create_meta_agent(
|
|
166
|
+
self,
|
|
167
|
+
class_name,
|
|
168
|
+
alliance,
|
|
169
|
+
Agent,
|
|
170
|
+
meta_attributes={
|
|
171
|
+
"level": attributes[2],
|
|
172
|
+
"power": attributes[0],
|
|
173
|
+
"position": attributes[1],
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Update the network if a new meta agent instance created
|
|
178
|
+
if meta:
|
|
179
|
+
self.network.add_node(
|
|
180
|
+
meta.unique_id,
|
|
181
|
+
size=(meta.level + 1) * 300,
|
|
182
|
+
level=meta.level,
|
|
183
|
+
)
|
|
184
|
+
self.add_link(meta, meta.agents)
|
|
@@ -53,7 +53,7 @@ class EpsteinCivilViolence(mesa.Model):
|
|
|
53
53
|
self.movement = movement
|
|
54
54
|
self.max_iters = max_iters
|
|
55
55
|
|
|
56
|
-
self.grid = mesa.
|
|
56
|
+
self.grid = mesa.discrete_space.OrthogonalVonNeumannGrid(
|
|
57
57
|
(width, height), capacity=1, torus=True, random=self.random
|
|
58
58
|
)
|
|
59
59
|
|
|
@@ -17,13 +17,11 @@ The Demographic Prisoner's Dilemma demonstrates how simple rules can lead to the
|
|
|
17
17
|
|
|
18
18
|
## How to Run
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
To run the model interactively, in this directory, run the following command
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
Launch the ``Demographic Prisoner's Dilemma Activation Schedule.ipynb`` notebook and run the code.
|
|
22
|
+
```
|
|
23
|
+
$ solara run app.py
|
|
24
|
+
```
|
|
27
25
|
|
|
28
26
|
## Files
|
|
29
27
|
|
|
@@ -42,14 +42,13 @@ The model demonstrates several Mesa concepts and features:
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
## How to Run
|
|
45
|
-
|
|
45
|
+
|
|
46
|
+
To run the model interactively, in this directory, run the following command
|
|
46
47
|
|
|
47
48
|
```
|
|
48
|
-
|
|
49
|
+
$ solara run app.py
|
|
49
50
|
```
|
|
50
51
|
|
|
51
|
-
Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run.
|
|
52
|
-
|
|
53
52
|
## Files
|
|
54
53
|
|
|
55
54
|
* `model.py`: The Sugarscape Constant Growback with Traders model.
|
|
@@ -58,7 +57,7 @@ Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and p
|
|
|
58
57
|
* `sugar_map.txt`: Provides sugar and spice landscape in raster type format.
|
|
59
58
|
* `tests.py`: Has tests to ensure that the model reproduces the results in shown in Growing Artificial Societies.
|
|
60
59
|
|
|
61
|
-
##
|
|
60
|
+
## Further Reading
|
|
62
61
|
|
|
63
62
|
- [Growing Artificial Societies](https://mitpress.mit.edu/9780262550253/growing-artificial-societies/)
|
|
64
63
|
- [Complexity Explorer Sugarscape with Traders Tutorial](https://www.complexityexplorer.org/courses/172-agent-based-models-with-python-an-introduction-to-mesa)
|
|
@@ -3,9 +3,9 @@ from pathlib import Path
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
|
|
5
5
|
import mesa
|
|
6
|
+
from mesa.discrete_space import OrthogonalVonNeumannGrid
|
|
7
|
+
from mesa.discrete_space.property_layer import PropertyLayer
|
|
6
8
|
from mesa.examples.advanced.sugarscape_g1mt.agents import Trader
|
|
7
|
-
from mesa.experimental.cell_space import OrthogonalVonNeumannGrid
|
|
8
|
-
from mesa.experimental.cell_space.property_layer import PropertyLayer
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
# Helper Functions
|
|
@@ -14,29 +14,14 @@ The model is tests and demonstrates several Mesa concepts and features:
|
|
|
14
14
|
- Writing a model composed of multiple files.
|
|
15
15
|
- Dynamically adding and removing agents from the schedule
|
|
16
16
|
|
|
17
|
-
## Installation
|
|
18
|
-
|
|
19
|
-
To install the dependencies use pip and the requirements.txt in this directory. e.g.
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
# First, we clone the Mesa repo
|
|
23
|
-
$ git clone https://github.com/projectmesa/mesa.git
|
|
24
|
-
$ cd mesa
|
|
25
|
-
# Then we cd to the example directory
|
|
26
|
-
$ cd examples/wolf_sheep
|
|
27
|
-
$ pip install -r requirements.txt
|
|
28
|
-
```
|
|
29
|
-
|
|
30
17
|
## How to Run
|
|
31
18
|
|
|
32
|
-
To run the model interactively,
|
|
19
|
+
To run the model interactively, in this directory, run the following command
|
|
33
20
|
|
|
34
21
|
```
|
|
35
|
-
$
|
|
22
|
+
$ solara run app.py
|
|
36
23
|
```
|
|
37
24
|
|
|
38
|
-
Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run.
|
|
39
|
-
|
|
40
25
|
## Files
|
|
41
26
|
|
|
42
27
|
* ``wolf_sheep/random_walk.py``: This defines the ``RandomWalker`` agent, which implements the behavior of moving randomly across a grid, one cell at a time. Both the Wolf and Sheep agents will inherit from it.
|
|
@@ -2,6 +2,7 @@ from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf
|
|
|
2
2
|
from mesa.examples.advanced.wolf_sheep.model import WolfSheep
|
|
3
3
|
from mesa.experimental.devs import ABMSimulator
|
|
4
4
|
from mesa.visualization import (
|
|
5
|
+
CommandConsole,
|
|
5
6
|
Slider,
|
|
6
7
|
SolaraViz,
|
|
7
8
|
make_plot_component,
|
|
@@ -87,7 +88,7 @@ model = WolfSheep(simulator=simulator, grass=True)
|
|
|
87
88
|
|
|
88
89
|
page = SolaraViz(
|
|
89
90
|
model,
|
|
90
|
-
components=[space_component, lineplot_component],
|
|
91
|
+
components=[space_component, lineplot_component, CommandConsole],
|
|
91
92
|
model_params=model_params,
|
|
92
93
|
name="Wolf Sheep",
|
|
93
94
|
simulator=simulator,
|
|
@@ -13,8 +13,8 @@ import math
|
|
|
13
13
|
|
|
14
14
|
from mesa import Model
|
|
15
15
|
from mesa.datacollection import DataCollector
|
|
16
|
+
from mesa.discrete_space import OrthogonalVonNeumannGrid
|
|
16
17
|
from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf
|
|
17
|
-
from mesa.experimental.cell_space import OrthogonalVonNeumannGrid
|
|
18
18
|
from mesa.experimental.devs import ABMSimulator
|
|
19
19
|
|
|
20
20
|
|
|
@@ -8,7 +8,12 @@ This model tests Mesa's continuous space feature, and uses numpy arrays to repre
|
|
|
8
8
|
|
|
9
9
|
## How to Run
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
To run the model interactively, in this directory, run the following command
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
$ solara run app.py
|
|
15
|
+
```
|
|
16
|
+
|
|
12
17
|
|
|
13
18
|
## Files
|
|
14
19
|
|
|
@@ -58,6 +58,7 @@ class Boid(ContinuousSpaceAgent):
|
|
|
58
58
|
self.separate_factor = separate
|
|
59
59
|
self.match_factor = match
|
|
60
60
|
self.neighbors = []
|
|
61
|
+
self.angle = 0.0 # represents the angle at which the boid is moving
|
|
61
62
|
|
|
62
63
|
def step(self):
|
|
63
64
|
"""Get the Boid's neighbors, compute the new vector, and move accordingly."""
|
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
3
|
|
|
4
|
+
from matplotlib.markers import MarkerStyle
|
|
5
|
+
|
|
4
6
|
sys.path.insert(0, os.path.abspath("../../../.."))
|
|
5
7
|
|
|
6
8
|
from mesa.examples.basic.boid_flockers.model import BoidFlockers
|
|
7
9
|
from mesa.visualization import Slider, SolaraViz, make_space_component
|
|
8
10
|
|
|
11
|
+
# Pre-compute markers for different angles (e.g., every 10 degrees)
|
|
12
|
+
MARKER_CACHE = {}
|
|
13
|
+
for angle in range(0, 360, 10):
|
|
14
|
+
marker = MarkerStyle(10)
|
|
15
|
+
marker._transform = marker.get_transform().rotate_deg(angle)
|
|
16
|
+
MARKER_CACHE[angle] = marker
|
|
17
|
+
|
|
9
18
|
|
|
10
19
|
def boid_draw(agent):
|
|
11
20
|
neighbors = len(agent.neighbors)
|
|
12
21
|
|
|
22
|
+
# Calculate the angle
|
|
23
|
+
deg = agent.angle
|
|
24
|
+
# Round to nearest 10 degrees
|
|
25
|
+
rounded_deg = round(deg / 10) * 10 % 360
|
|
26
|
+
|
|
27
|
+
# using cached markers to speed things up
|
|
13
28
|
if neighbors <= 1:
|
|
14
|
-
return {"color": "red", "size": 20}
|
|
29
|
+
return {"color": "red", "size": 20, "marker": MARKER_CACHE[rounded_deg]}
|
|
15
30
|
elif neighbors >= 2:
|
|
16
|
-
return {"color": "green", "size": 20}
|
|
31
|
+
return {"color": "green", "size": 20, "marker": MARKER_CACHE[rounded_deg]}
|
|
17
32
|
|
|
18
33
|
|
|
19
34
|
model_params = {
|
|
@@ -49,6 +49,9 @@ class BoidFlockers(Model):
|
|
|
49
49
|
seed: Random seed for reproducibility (default: None)
|
|
50
50
|
"""
|
|
51
51
|
super().__init__(seed=seed)
|
|
52
|
+
self.agent_angles = np.zeros(
|
|
53
|
+
population_size
|
|
54
|
+
) # holds the angle representing the direction of all agents at a given step
|
|
52
55
|
|
|
53
56
|
# Set up the space
|
|
54
57
|
self.space = ContinuousSpace(
|
|
@@ -79,6 +82,14 @@ class BoidFlockers(Model):
|
|
|
79
82
|
self.average_heading = None
|
|
80
83
|
self.update_average_heading()
|
|
81
84
|
|
|
85
|
+
# vectorizing the calculation of angles for all agents
|
|
86
|
+
def calculate_angles(self):
|
|
87
|
+
d1 = np.array([agent.direction[0] for agent in self.agents])
|
|
88
|
+
d2 = np.array([agent.direction[1] for agent in self.agents])
|
|
89
|
+
self.agent_angles = np.degrees(np.arctan2(d1, d2))
|
|
90
|
+
for agent, angle in zip(self.agents, self.agent_angles):
|
|
91
|
+
agent.angle = angle
|
|
92
|
+
|
|
82
93
|
def update_average_heading(self):
|
|
83
94
|
"""Calculate the average heading (direction) of all Boids."""
|
|
84
95
|
if not self.agents:
|
|
@@ -96,3 +107,4 @@ class BoidFlockers(Model):
|
|
|
96
107
|
"""
|
|
97
108
|
self.agents.shuffle_do("step")
|
|
98
109
|
self.update_average_heading()
|
|
110
|
+
self.calculate_angles()
|
|
@@ -2,25 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Summary
|
|
4
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.
|
|
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.
|
|
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. Mesa's [Getting Started](https://mesa.readthedocs.io/latest/getting_started.html) section walks through the Boltzmann Wealth Model in a series of short introductory tutorials, starting with[Creating your First Model](https://mesa.readthedocs.io/latest/tutorials/0_first_model.html).
|
|
8
6
|
|
|
9
7
|
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
8
|
|
|
11
9
|
## How to Run
|
|
12
10
|
|
|
13
|
-
To
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
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:
|
|
11
|
+
To run the model interactively, in this directory, run the following command
|
|
17
12
|
|
|
18
13
|
```
|
|
19
14
|
$ solara run app.py
|
|
20
15
|
```
|
|
21
16
|
|
|
22
|
-
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.
|
|
23
|
-
|
|
24
17
|
|
|
25
18
|
## Files
|
|
26
19
|
|
|
@@ -46,9 +39,6 @@ Then, you can run the Streamlit app using the following command:
|
|
|
46
39
|
|
|
47
40
|
## Further Reading
|
|
48
41
|
|
|
49
|
-
The full tutorial describing how the model is built can be found at:
|
|
50
|
-
https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html
|
|
51
|
-
|
|
52
42
|
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:
|
|
53
43
|
|
|
54
44
|
[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)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from mesa import
|
|
1
|
+
from mesa.discrete_space import CellAgent
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
class MoneyAgent(
|
|
4
|
+
class MoneyAgent(CellAgent):
|
|
5
5
|
"""An agent with fixed initial wealth.
|
|
6
6
|
|
|
7
7
|
Each agent starts with 1 unit of wealth and can give 1 unit to other agents
|
|
@@ -11,28 +11,23 @@ class MoneyAgent(Agent):
|
|
|
11
11
|
wealth (int): The agent's current wealth (starts at 1)
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
def __init__(self, model):
|
|
14
|
+
def __init__(self, model, cell):
|
|
15
15
|
"""Create a new agent.
|
|
16
16
|
|
|
17
17
|
Args:
|
|
18
18
|
model (Model): The model instance that contains the agent
|
|
19
19
|
"""
|
|
20
20
|
super().__init__(model)
|
|
21
|
+
self.cell = cell
|
|
21
22
|
self.wealth = 1
|
|
22
23
|
|
|
23
24
|
def move(self):
|
|
24
25
|
"""Move the agent to a random neighboring cell."""
|
|
25
|
-
|
|
26
|
-
self.pos, moore=True, include_center=False
|
|
27
|
-
)
|
|
28
|
-
new_position = self.random.choice(possible_steps)
|
|
29
|
-
self.model.grid.move_agent(self, new_position)
|
|
26
|
+
self.cell = self.cell.neighborhood.select_random_cell()
|
|
30
27
|
|
|
31
28
|
def give_money(self):
|
|
32
29
|
"""Give 1 unit of wealth to a random agent in the same cell."""
|
|
33
|
-
cellmates = self.
|
|
34
|
-
# Remove self from potential recipients
|
|
35
|
-
cellmates.pop(cellmates.index(self))
|
|
30
|
+
cellmates = [a for a in self.cell.agents if a is not self]
|
|
36
31
|
|
|
37
32
|
if cellmates: # Only give money if there are other agents present
|
|
38
33
|
other = self.random.choice(cellmates)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from mesa.examples.basic.boltzmann_wealth_model.model import BoltzmannWealth
|
|
2
|
-
from mesa.mesa_logging import
|
|
2
|
+
from mesa.mesa_logging import INFO, log_to_stderr
|
|
3
3
|
from mesa.visualization import (
|
|
4
4
|
SolaraViz,
|
|
5
5
|
make_plot_component,
|
|
6
6
|
make_space_component,
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
-
log_to_stderr(
|
|
9
|
+
log_to_stderr(INFO)
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def agent_portrayal(agent):
|
|
@@ -9,8 +9,8 @@ when they occupy the same cell.
|
|
|
9
9
|
|
|
10
10
|
from mesa import Model
|
|
11
11
|
from mesa.datacollection import DataCollector
|
|
12
|
+
from mesa.discrete_space import OrthogonalMooreGrid
|
|
12
13
|
from mesa.examples.basic.boltzmann_wealth_model.agents import MoneyAgent
|
|
13
|
-
from mesa.space import MultiGrid
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class BoltzmannWealth(Model):
|
|
@@ -39,22 +39,18 @@ class BoltzmannWealth(Model):
|
|
|
39
39
|
super().__init__(seed=seed)
|
|
40
40
|
|
|
41
41
|
self.num_agents = n
|
|
42
|
-
self.grid =
|
|
42
|
+
self.grid = OrthogonalMooreGrid((width, height), random=self.random)
|
|
43
43
|
|
|
44
44
|
# Set up data collection
|
|
45
45
|
self.datacollector = DataCollector(
|
|
46
46
|
model_reporters={"Gini": self.compute_gini},
|
|
47
47
|
agent_reporters={"Wealth": "wealth"},
|
|
48
48
|
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
# Add agent to random grid cell
|
|
55
|
-
x = self.random.randrange(self.grid.width)
|
|
56
|
-
y = self.random.randrange(self.grid.height)
|
|
57
|
-
self.grid.place_agent(agent, (x, y))
|
|
49
|
+
MoneyAgent.create_agents(
|
|
50
|
+
self,
|
|
51
|
+
self.num_agents,
|
|
52
|
+
self.random.choices(self.grid.all_cells.cells, k=self.num_agents),
|
|
53
|
+
)
|
|
58
54
|
|
|
59
55
|
self.running = True
|
|
60
56
|
self.datacollector.collect(self)
|
|
@@ -9,20 +9,12 @@ The "game" is a zero-player game, meaning that its evolution is determined by it
|
|
|
9
9
|
|
|
10
10
|
## How to Run
|
|
11
11
|
|
|
12
|
-
To run the model interactively
|
|
12
|
+
To run the model interactively, in this directory, run the following command
|
|
13
13
|
|
|
14
14
|
```
|
|
15
15
|
$ solara run app.py
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
For streamlit, you need
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
$ streamlit run st_app.py
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
This will open your browser and show you the controls. You can start the model by hitting the run button.
|
|
25
|
-
|
|
26
18
|
## Files
|
|
27
19
|
|
|
28
20
|
* ``agents.py``: Defines the behavior of an individual cell, which can be in two states: DEAD or ALIVE.
|
|
@@ -1,16 +1,24 @@
|
|
|
1
|
-
from mesa import
|
|
1
|
+
from mesa.discrete_space import FixedAgent
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
class Cell(
|
|
4
|
+
class Cell(FixedAgent):
|
|
5
5
|
"""Represents a single ALIVE or DEAD cell in the simulation."""
|
|
6
6
|
|
|
7
7
|
DEAD = 0
|
|
8
8
|
ALIVE = 1
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
@property
|
|
11
|
+
def x(self):
|
|
12
|
+
return self.cell.coordinate[0]
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def y(self):
|
|
16
|
+
return self.cell.coordinate[1]
|
|
17
|
+
|
|
18
|
+
def __init__(self, model, cell, init_state=DEAD):
|
|
11
19
|
"""Create a cell, in the given state, at the given x, y position."""
|
|
12
20
|
super().__init__(model)
|
|
13
|
-
self.
|
|
21
|
+
self.cell = cell
|
|
14
22
|
self.state = init_state
|
|
15
23
|
self._next_state = None
|
|
16
24
|
|
|
@@ -20,7 +28,7 @@ class Cell(Agent):
|
|
|
20
28
|
|
|
21
29
|
@property
|
|
22
30
|
def neighbors(self):
|
|
23
|
-
return self.
|
|
31
|
+
return self.cell.neighborhood.agents
|
|
24
32
|
|
|
25
33
|
def determine_state(self):
|
|
26
34
|
"""Compute if the cell will be dead or alive at the next tick. This is
|