Mesa 3.0.0b2__py3-none-any.whl → 3.0.1__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.
- mesa/__init__.py +1 -1
- mesa/agent.py +15 -3
- mesa/batchrunner.py +26 -1
- mesa/examples/README.md +11 -11
- mesa/examples/__init__.py +2 -2
- mesa/examples/advanced/epstein_civil_violence/agents.py +44 -38
- mesa/examples/advanced/epstein_civil_violence/app.py +29 -28
- mesa/examples/advanced/epstein_civil_violence/model.py +33 -65
- mesa/examples/advanced/pd_grid/app.py +9 -5
- mesa/examples/advanced/pd_grid/model.py +1 -1
- mesa/examples/advanced/sugarscape_g1mt/app.py +5 -13
- mesa/examples/advanced/sugarscape_g1mt/model.py +3 -1
- mesa/examples/advanced/wolf_sheep/agents.py +53 -39
- mesa/examples/advanced/wolf_sheep/app.py +37 -19
- mesa/examples/advanced/wolf_sheep/model.py +68 -74
- mesa/examples/basic/boid_flockers/agents.py +49 -18
- mesa/examples/basic/boid_flockers/app.py +2 -2
- mesa/examples/basic/boid_flockers/model.py +55 -19
- mesa/examples/basic/boltzmann_wealth_model/agents.py +23 -5
- mesa/examples/basic/boltzmann_wealth_model/app.py +22 -13
- mesa/examples/basic/boltzmann_wealth_model/model.py +48 -13
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +2 -2
- mesa/examples/basic/conways_game_of_life/app.py +15 -3
- mesa/examples/basic/schelling/agents.py +9 -5
- mesa/examples/basic/schelling/app.py +5 -5
- mesa/examples/basic/schelling/model.py +48 -26
- mesa/examples/basic/virus_on_network/app.py +25 -47
- mesa/experimental/cell_space/cell_collection.py +14 -2
- mesa/experimental/cell_space/discrete_space.py +16 -2
- mesa/experimental/devs/simulator.py +59 -14
- mesa/model.py +4 -4
- mesa/space.py +0 -30
- mesa/time.py +4 -4
- mesa/visualization/__init__.py +17 -6
- mesa/visualization/components/__init__.py +83 -0
- mesa/visualization/components/{altair.py → altair_components.py} +34 -2
- mesa/visualization/components/matplotlib_components.py +175 -0
- mesa/visualization/mpl_space_drawing.py +593 -0
- mesa/visualization/solara_viz.py +156 -67
- {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/METADATA +6 -8
- {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/RECORD +46 -44
- {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/WHEEL +1 -1
- mesa/visualization/components/matplotlib.py +0 -386
- /mesa/visualization/{UserParam.py → user_param.py} +0 -0
- {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/entry_points.txt +0 -0
- {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/licenses/LICENSE +0 -0
- {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,60 +1,81 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
1
|
+
"""
|
|
2
|
+
Boids Flocking Model
|
|
3
|
+
===================
|
|
3
4
|
A Mesa implementation of Craig Reynolds's Boids flocker model.
|
|
4
5
|
Uses numpy arrays to represent vectors.
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
8
|
import numpy as np
|
|
8
9
|
|
|
9
|
-
import
|
|
10
|
+
from mesa import Model
|
|
10
11
|
from mesa.examples.basic.boid_flockers.agents import Boid
|
|
12
|
+
from mesa.space import ContinuousSpace
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
class BoidFlockers(
|
|
15
|
+
class BoidFlockers(Model):
|
|
14
16
|
"""Flocker model class. Handles agent creation, placement and scheduling."""
|
|
15
17
|
|
|
16
18
|
def __init__(
|
|
17
19
|
self,
|
|
18
|
-
seed=None,
|
|
19
20
|
population=100,
|
|
20
21
|
width=100,
|
|
21
22
|
height=100,
|
|
22
|
-
vision=10,
|
|
23
23
|
speed=1,
|
|
24
|
-
|
|
24
|
+
vision=10,
|
|
25
|
+
separation=2,
|
|
25
26
|
cohere=0.03,
|
|
26
27
|
separate=0.015,
|
|
27
28
|
match=0.05,
|
|
29
|
+
seed=None,
|
|
28
30
|
):
|
|
29
|
-
"""Create a new
|
|
31
|
+
"""Create a new Boids Flocking model.
|
|
30
32
|
|
|
31
33
|
Args:
|
|
32
|
-
population: Number of Boids
|
|
33
|
-
width
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
cohere
|
|
39
|
-
|
|
34
|
+
population: Number of Boids in the simulation (default: 100)
|
|
35
|
+
width: Width of the space (default: 100)
|
|
36
|
+
height: Height of the space (default: 100)
|
|
37
|
+
speed: How fast the Boids move (default: 1)
|
|
38
|
+
vision: How far each Boid can see (default: 10)
|
|
39
|
+
separation: Minimum distance between Boids (default: 2)
|
|
40
|
+
cohere: Weight of cohesion behavior (default: 0.03)
|
|
41
|
+
separate: Weight of separation behavior (default: 0.015)
|
|
42
|
+
match: Weight of alignment behavior (default: 0.05)
|
|
43
|
+
seed: Random seed for reproducibility (default: None)
|
|
40
44
|
"""
|
|
41
45
|
super().__init__(seed=seed)
|
|
46
|
+
|
|
47
|
+
# Model Parameters
|
|
42
48
|
self.population = population
|
|
43
49
|
self.vision = vision
|
|
44
50
|
self.speed = speed
|
|
45
51
|
self.separation = separation
|
|
46
52
|
|
|
47
|
-
|
|
53
|
+
# Set up the space
|
|
54
|
+
self.space = ContinuousSpace(width, height, torus=True)
|
|
55
|
+
|
|
56
|
+
# Store flocking weights
|
|
48
57
|
self.factors = {"cohere": cohere, "separate": separate, "match": match}
|
|
58
|
+
|
|
59
|
+
# Create and place the Boid agents
|
|
49
60
|
self.make_agents()
|
|
50
61
|
|
|
62
|
+
# For tracking statistics
|
|
63
|
+
self.average_heading = None
|
|
64
|
+
self.update_average_heading()
|
|
65
|
+
|
|
51
66
|
def make_agents(self):
|
|
52
|
-
"""Create
|
|
67
|
+
"""Create and place all Boid agents randomly in the space."""
|
|
53
68
|
for _ in range(self.population):
|
|
69
|
+
# Random position
|
|
54
70
|
x = self.random.random() * self.space.x_max
|
|
55
71
|
y = self.random.random() * self.space.y_max
|
|
56
72
|
pos = np.array((x, y))
|
|
57
|
-
|
|
73
|
+
|
|
74
|
+
# Random initial direction
|
|
75
|
+
direction = np.random.random(2) * 2 - 1 # Random vector between -1 and 1
|
|
76
|
+
direction /= np.linalg.norm(direction) # Normalize
|
|
77
|
+
|
|
78
|
+
# Create and place the Boid
|
|
58
79
|
boid = Boid(
|
|
59
80
|
model=self,
|
|
60
81
|
speed=self.speed,
|
|
@@ -65,5 +86,20 @@ class BoidFlockers(mesa.Model):
|
|
|
65
86
|
)
|
|
66
87
|
self.space.place_agent(boid, pos)
|
|
67
88
|
|
|
89
|
+
def update_average_heading(self):
|
|
90
|
+
"""Calculate the average heading (direction) of all Boids."""
|
|
91
|
+
if not self.agents:
|
|
92
|
+
self.average_heading = 0
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
headings = np.array([agent.direction for agent in self.agents])
|
|
96
|
+
mean_heading = np.mean(headings, axis=0)
|
|
97
|
+
self.average_heading = np.arctan2(mean_heading[1], mean_heading[0])
|
|
98
|
+
|
|
68
99
|
def step(self):
|
|
100
|
+
"""Run one step of the model.
|
|
101
|
+
|
|
102
|
+
All agents are activated in random order using the AgentSet shuffle_do method.
|
|
103
|
+
"""
|
|
69
104
|
self.agents.shuffle_do("step")
|
|
105
|
+
self.update_average_heading()
|
|
@@ -2,13 +2,26 @@ from mesa import Agent
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class MoneyAgent(Agent):
|
|
5
|
-
"""An agent with fixed initial wealth.
|
|
5
|
+
"""An agent with fixed initial wealth.
|
|
6
|
+
|
|
7
|
+
Each agent starts with 1 unit of wealth and can give 1 unit to other agents
|
|
8
|
+
if they occupy the same cell.
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
wealth (int): The agent's current wealth (starts at 1)
|
|
12
|
+
"""
|
|
6
13
|
|
|
7
14
|
def __init__(self, model):
|
|
15
|
+
"""Create a new agent.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
model (Model): The model instance that contains the agent
|
|
19
|
+
"""
|
|
8
20
|
super().__init__(model)
|
|
9
21
|
self.wealth = 1
|
|
10
22
|
|
|
11
23
|
def move(self):
|
|
24
|
+
"""Move the agent to a random neighboring cell."""
|
|
12
25
|
possible_steps = self.model.grid.get_neighborhood(
|
|
13
26
|
self.pos, moore=True, include_center=False
|
|
14
27
|
)
|
|
@@ -16,16 +29,21 @@ class MoneyAgent(Agent):
|
|
|
16
29
|
self.model.grid.move_agent(self, new_position)
|
|
17
30
|
|
|
18
31
|
def give_money(self):
|
|
32
|
+
"""Give 1 unit of wealth to a random agent in the same cell."""
|
|
19
33
|
cellmates = self.model.grid.get_cell_list_contents([self.pos])
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if
|
|
34
|
+
# Remove self from potential recipients
|
|
35
|
+
cellmates.pop(cellmates.index(self))
|
|
36
|
+
|
|
37
|
+
if cellmates: # Only give money if there are other agents present
|
|
24
38
|
other = self.random.choice(cellmates)
|
|
25
39
|
other.wealth += 1
|
|
26
40
|
self.wealth -= 1
|
|
27
41
|
|
|
28
42
|
def step(self):
|
|
43
|
+
"""Execute one step for the agent:
|
|
44
|
+
1. Move to a neighboring cell
|
|
45
|
+
2. If wealth > 0, maybe give money to another agent in the same cell
|
|
46
|
+
"""
|
|
29
47
|
self.move()
|
|
30
48
|
if self.wealth > 0:
|
|
31
49
|
self.give_money()
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
from mesa.examples.basic.boltzmann_wealth_model.model import
|
|
1
|
+
from mesa.examples.basic.boltzmann_wealth_model.model import BoltzmannWealth
|
|
2
2
|
from mesa.visualization import (
|
|
3
3
|
SolaraViz,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
make_plot_component,
|
|
5
|
+
make_space_component,
|
|
6
6
|
)
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def agent_portrayal(agent):
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if agent.wealth > 0:
|
|
13
|
-
size = 50
|
|
14
|
-
color = "tab:blue"
|
|
15
|
-
return {"size": size, "color": color}
|
|
10
|
+
color = agent.wealth # we are using a colormap to translate wealth to color
|
|
11
|
+
return {"color": color}
|
|
16
12
|
|
|
17
13
|
|
|
18
14
|
model_params = {
|
|
@@ -24,20 +20,33 @@ model_params = {
|
|
|
24
20
|
"max": 100,
|
|
25
21
|
"step": 1,
|
|
26
22
|
},
|
|
23
|
+
"seed": {
|
|
24
|
+
"type": "InputText",
|
|
25
|
+
"value": 42,
|
|
26
|
+
"label": "Random Seed",
|
|
27
|
+
},
|
|
27
28
|
"width": 10,
|
|
28
29
|
"height": 10,
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
|
|
33
|
+
def post_process(ax):
|
|
34
|
+
ax.get_figure().colorbar(ax.collections[0], label="wealth", ax=ax)
|
|
35
|
+
|
|
36
|
+
|
|
31
37
|
# Create initial model instance
|
|
32
|
-
|
|
38
|
+
model = BoltzmannWealth(50, 10, 10)
|
|
33
39
|
|
|
34
40
|
# Create visualization elements. The visualization elements are solara components
|
|
35
41
|
# that receive the model instance as a "prop" and display it in a certain way.
|
|
36
42
|
# Under the hood these are just classes that receive the model instance.
|
|
37
43
|
# You can also author your own visualization elements, which can also be functions
|
|
38
44
|
# that receive the model instance and return a valid solara component.
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
|
|
46
|
+
SpaceGraph = make_space_component(
|
|
47
|
+
agent_portrayal, cmap="viridis", vmin=0, vmax=10, post_process=post_process
|
|
48
|
+
)
|
|
49
|
+
GiniPlot = make_plot_component("Gini")
|
|
41
50
|
|
|
42
51
|
# Create the SolaraViz page. This will automatically create a server and display the
|
|
43
52
|
# visualization elements in a web browser.
|
|
@@ -45,7 +54,7 @@ GiniPlot = make_plot_measure("Gini")
|
|
|
45
54
|
# solara run app.py
|
|
46
55
|
# It will automatically update and display any changes made to this file
|
|
47
56
|
page = SolaraViz(
|
|
48
|
-
|
|
57
|
+
model,
|
|
49
58
|
components=[SpaceGraph, GiniPlot],
|
|
50
59
|
model_params=model_params,
|
|
51
60
|
name="Boltzmann Wealth Model",
|
|
@@ -1,43 +1,78 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
|
+
Boltzmann Wealth Model
|
|
3
|
+
=====================
|
|
4
|
+
|
|
5
|
+
A simple model of wealth distribution based on the Boltzmann-Gibbs distribution.
|
|
6
|
+
Agents move randomly on a grid, giving one unit of wealth to a random neighbor
|
|
7
|
+
when they occupy the same cell.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from mesa import Model
|
|
11
|
+
from mesa.datacollection import DataCollector
|
|
2
12
|
from mesa.examples.basic.boltzmann_wealth_model.agents import MoneyAgent
|
|
13
|
+
from mesa.space import MultiGrid
|
|
3
14
|
|
|
4
15
|
|
|
5
|
-
class
|
|
16
|
+
class BoltzmannWealth(Model):
|
|
6
17
|
"""A simple model of an economy where agents exchange currency at random.
|
|
7
18
|
|
|
8
|
-
All
|
|
9
|
-
a unit of currency to another agent
|
|
10
|
-
highly skewed distribution of wealth.
|
|
19
|
+
All agents begin with one unit of currency, and each time step agents can give
|
|
20
|
+
a unit of currency to another agent in the same cell. Over time, this produces
|
|
21
|
+
a highly skewed distribution of wealth.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
num_agents (int): Number of agents in the model
|
|
25
|
+
grid (MultiGrid): The space in which agents move
|
|
26
|
+
running (bool): Whether the model should continue running
|
|
27
|
+
datacollector (DataCollector): Collects and stores model data
|
|
11
28
|
"""
|
|
12
29
|
|
|
13
30
|
def __init__(self, n=100, width=10, height=10, seed=None):
|
|
31
|
+
"""Initialize the model.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
n (int, optional): Number of agents. Defaults to 100.
|
|
35
|
+
width (int, optional): Grid width. Defaults to 10.
|
|
36
|
+
height (int, optional): Grid height. Defaults to 10.
|
|
37
|
+
seed (int, optional): Random seed. Defaults to None.
|
|
38
|
+
"""
|
|
14
39
|
super().__init__(seed=seed)
|
|
40
|
+
|
|
15
41
|
self.num_agents = n
|
|
16
|
-
self.grid =
|
|
42
|
+
self.grid = MultiGrid(width, height, torus=True)
|
|
17
43
|
|
|
18
|
-
|
|
44
|
+
# Set up data collection
|
|
45
|
+
self.datacollector = DataCollector(
|
|
19
46
|
model_reporters={"Gini": self.compute_gini},
|
|
20
47
|
agent_reporters={"Wealth": "wealth"},
|
|
21
48
|
)
|
|
22
|
-
|
|
49
|
+
|
|
50
|
+
# Create and place the agents
|
|
23
51
|
for _ in range(self.num_agents):
|
|
24
|
-
|
|
52
|
+
agent = MoneyAgent(self)
|
|
25
53
|
|
|
26
|
-
# Add
|
|
54
|
+
# Add agent to random grid cell
|
|
27
55
|
x = self.random.randrange(self.grid.width)
|
|
28
56
|
y = self.random.randrange(self.grid.height)
|
|
29
|
-
self.grid.place_agent(
|
|
57
|
+
self.grid.place_agent(agent, (x, y))
|
|
30
58
|
|
|
31
59
|
self.running = True
|
|
32
60
|
self.datacollector.collect(self)
|
|
33
61
|
|
|
34
62
|
def step(self):
|
|
35
|
-
self.agents.shuffle_do("step")
|
|
36
|
-
self.datacollector.collect(self)
|
|
63
|
+
self.agents.shuffle_do("step") # Activate all agents in random order
|
|
64
|
+
self.datacollector.collect(self) # Collect data
|
|
37
65
|
|
|
38
66
|
def compute_gini(self):
|
|
67
|
+
"""Calculate the Gini coefficient for the model's current wealth distribution.
|
|
68
|
+
|
|
69
|
+
The Gini coefficient is a measure of inequality in distributions.
|
|
70
|
+
- A Gini of 0 represents complete equality, where all agents have equal wealth.
|
|
71
|
+
- A Gini of 1 represents maximal inequality, where one agent has all wealth.
|
|
72
|
+
"""
|
|
39
73
|
agent_wealths = [agent.wealth for agent in self.agents]
|
|
40
74
|
x = sorted(agent_wealths)
|
|
41
75
|
n = self.num_agents
|
|
76
|
+
# Calculate using the standard formula for Gini coefficient
|
|
42
77
|
b = sum(xi * (n - i) for i, xi in enumerate(x)) / (n * sum(x))
|
|
43
78
|
return 1 + (1 / n) - 2 * b
|
|
@@ -5,7 +5,7 @@ import time
|
|
|
5
5
|
import altair as alt
|
|
6
6
|
import pandas as pd
|
|
7
7
|
import streamlit as st
|
|
8
|
-
from model import
|
|
8
|
+
from model import BoltzmannWealth
|
|
9
9
|
|
|
10
10
|
model = st.title("Boltzman Wealth Model")
|
|
11
11
|
num_agents = st.slider(
|
|
@@ -19,7 +19,7 @@ num_ticks = st.slider(
|
|
|
19
19
|
)
|
|
20
20
|
height = st.slider("Select Grid Height", min_value=10, max_value=100, step=10, value=15)
|
|
21
21
|
width = st.slider("Select Grid Width", min_value=10, max_value=100, step=10, value=20)
|
|
22
|
-
model =
|
|
22
|
+
model = BoltzmannWealth(num_agents, height, width)
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
status_text = st.empty()
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
from mesa.examples.basic.conways_game_of_life.model import ConwaysGameOfLife
|
|
2
2
|
from mesa.visualization import (
|
|
3
3
|
SolaraViz,
|
|
4
|
-
|
|
4
|
+
make_space_component,
|
|
5
5
|
)
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def agent_portrayal(agent):
|
|
9
|
-
return {
|
|
9
|
+
return {
|
|
10
|
+
"color": "white" if agent.state == 0 else "black",
|
|
11
|
+
"marker": "s",
|
|
12
|
+
"size": 25,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def post_process(ax):
|
|
17
|
+
ax.set_aspect("equal")
|
|
18
|
+
ax.set_xticks([])
|
|
19
|
+
ax.set_yticks([])
|
|
10
20
|
|
|
11
21
|
|
|
12
22
|
model_params = {
|
|
@@ -22,7 +32,9 @@ model1 = ConwaysGameOfLife(50, 50)
|
|
|
22
32
|
# Under the hood these are just classes that receive the model instance.
|
|
23
33
|
# You can also author your own visualization elements, which can also be functions
|
|
24
34
|
# that receive the model instance and return a valid solara component.
|
|
25
|
-
SpaceGraph =
|
|
35
|
+
SpaceGraph = make_space_component(
|
|
36
|
+
agent_portrayal, post_process=post_process, draw_grid=False
|
|
37
|
+
)
|
|
26
38
|
|
|
27
39
|
|
|
28
40
|
# Create the SolaraViz page. This will automatically create a server and display the
|
|
@@ -1,25 +1,29 @@
|
|
|
1
|
-
from mesa import Agent
|
|
1
|
+
from mesa import Agent
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class SchellingAgent(Agent):
|
|
5
5
|
"""Schelling segregation agent."""
|
|
6
6
|
|
|
7
|
-
def __init__(self, model
|
|
7
|
+
def __init__(self, model, agent_type: int) -> None:
|
|
8
8
|
"""Create a new Schelling agent.
|
|
9
9
|
|
|
10
10
|
Args:
|
|
11
|
-
|
|
11
|
+
model: The model instance the agent belongs to
|
|
12
|
+
agent_type: Indicator for the agent's type (minority=1, majority=0)
|
|
12
13
|
"""
|
|
13
14
|
super().__init__(model)
|
|
14
15
|
self.type = agent_type
|
|
15
16
|
|
|
16
17
|
def step(self) -> None:
|
|
18
|
+
"""Determine if agent is happy and move if necessary."""
|
|
17
19
|
neighbors = self.model.grid.iter_neighbors(
|
|
18
20
|
self.pos, moore=True, radius=self.model.radius
|
|
19
21
|
)
|
|
20
|
-
similar = sum(1 for neighbor in neighbors if neighbor.type == self.type)
|
|
21
22
|
|
|
22
|
-
#
|
|
23
|
+
# Count similar neighbors
|
|
24
|
+
similar = sum(neighbor.type == self.type for neighbor in neighbors)
|
|
25
|
+
|
|
26
|
+
# If unhappy, move to a random empty cell:
|
|
23
27
|
if similar < self.model.homophily:
|
|
24
28
|
self.model.grid.move_to_empty(self)
|
|
25
29
|
else:
|
|
@@ -4,8 +4,8 @@ from mesa.examples.basic.schelling.model import Schelling
|
|
|
4
4
|
from mesa.visualization import (
|
|
5
5
|
Slider,
|
|
6
6
|
SolaraViz,
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
make_plot_component,
|
|
8
|
+
make_space_component,
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
|
|
@@ -28,13 +28,13 @@ model_params = {
|
|
|
28
28
|
|
|
29
29
|
model1 = Schelling(20, 20, 0.8, 0.2, 3)
|
|
30
30
|
|
|
31
|
-
HappyPlot =
|
|
31
|
+
HappyPlot = make_plot_component({"happy": "tab:green"})
|
|
32
32
|
|
|
33
33
|
page = SolaraViz(
|
|
34
34
|
model1,
|
|
35
35
|
components=[
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
make_space_component(agent_portrayal),
|
|
37
|
+
HappyPlot,
|
|
38
38
|
get_happy_agents,
|
|
39
39
|
],
|
|
40
40
|
model_params=model_params,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import mesa
|
|
2
1
|
from mesa import Model
|
|
2
|
+
from mesa.datacollection import DataCollector
|
|
3
3
|
from mesa.examples.basic.schelling.agents import SchellingAgent
|
|
4
|
+
from mesa.space import SingleGrid
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class Schelling(Model):
|
|
@@ -8,52 +9,73 @@ class Schelling(Model):
|
|
|
8
9
|
|
|
9
10
|
def __init__(
|
|
10
11
|
self,
|
|
11
|
-
height=
|
|
12
|
-
width=
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
height: int = 40,
|
|
13
|
+
width: int = 40,
|
|
14
|
+
density: float = 0.8,
|
|
15
|
+
minority_pc: float = 0.5,
|
|
16
|
+
homophily: int = 3,
|
|
17
|
+
radius: int = 1,
|
|
17
18
|
seed=None,
|
|
18
19
|
):
|
|
19
20
|
"""Create a new Schelling model.
|
|
20
21
|
|
|
21
22
|
Args:
|
|
22
|
-
width
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
width: Width of the grid
|
|
24
|
+
height: Height of the grid
|
|
25
|
+
density: Initial chance for a cell to be populated (0-1)
|
|
26
|
+
minority_pc: Chance for an agent to be in minority class (0-1)
|
|
27
|
+
homophily: Minimum number of similar neighbors needed for happiness
|
|
28
|
+
radius: Search radius for checking neighbor similarity
|
|
29
|
+
seed: Seed for reproducibility
|
|
28
30
|
"""
|
|
29
31
|
super().__init__(seed=seed)
|
|
32
|
+
|
|
33
|
+
# Model parameters
|
|
34
|
+
self.height = height
|
|
35
|
+
self.width = width
|
|
36
|
+
self.density = density
|
|
37
|
+
self.minority_pc = minority_pc
|
|
30
38
|
self.homophily = homophily
|
|
31
39
|
self.radius = radius
|
|
32
40
|
|
|
33
|
-
|
|
41
|
+
# Initialize grid
|
|
42
|
+
self.grid = SingleGrid(width, height, torus=True)
|
|
34
43
|
|
|
44
|
+
# Track happiness
|
|
35
45
|
self.happy = 0
|
|
36
|
-
|
|
37
|
-
|
|
46
|
+
|
|
47
|
+
# Set up data collection
|
|
48
|
+
self.datacollector = DataCollector(
|
|
49
|
+
model_reporters={
|
|
50
|
+
"happy": "happy",
|
|
51
|
+
"pct_happy": lambda m: (m.happy / len(m.agents)) * 100
|
|
52
|
+
if len(m.agents) > 0
|
|
53
|
+
else 0,
|
|
54
|
+
"population": lambda m: len(m.agents),
|
|
55
|
+
"minority_pct": lambda m: (
|
|
56
|
+
sum(1 for agent in m.agents if agent.type == 1)
|
|
57
|
+
/ len(m.agents)
|
|
58
|
+
* 100
|
|
59
|
+
if len(m.agents) > 0
|
|
60
|
+
else 0
|
|
61
|
+
),
|
|
62
|
+
},
|
|
63
|
+
agent_reporters={"agent_type": "type"},
|
|
38
64
|
)
|
|
39
65
|
|
|
40
|
-
#
|
|
41
|
-
# We use a grid iterator that returns
|
|
42
|
-
# the coordinates of a cell as well as
|
|
43
|
-
# its contents. (coord_iter)
|
|
66
|
+
# Create agents and place them on the grid
|
|
44
67
|
for _, pos in self.grid.coord_iter():
|
|
45
|
-
if self.random.random() < density:
|
|
68
|
+
if self.random.random() < self.density:
|
|
46
69
|
agent_type = 1 if self.random.random() < minority_pc else 0
|
|
47
70
|
agent = SchellingAgent(self, agent_type)
|
|
48
71
|
self.grid.place_agent(agent, pos)
|
|
49
72
|
|
|
73
|
+
# Collect initial state
|
|
50
74
|
self.datacollector.collect(self)
|
|
51
75
|
|
|
52
76
|
def step(self):
|
|
53
77
|
"""Run one step of the model."""
|
|
54
78
|
self.happy = 0 # Reset counter of happy agents
|
|
55
|
-
self.agents.shuffle_do("step")
|
|
56
|
-
|
|
57
|
-
self.
|
|
58
|
-
|
|
59
|
-
self.running = self.happy != len(self.agents)
|
|
79
|
+
self.agents.shuffle_do("step") # Activate all agents in random order
|
|
80
|
+
self.datacollector.collect(self) # Collect data
|
|
81
|
+
self.running = self.happy < len(self.agents) # Continue until everyone is happy
|
|
@@ -1,44 +1,27 @@
|
|
|
1
1
|
import math
|
|
2
2
|
|
|
3
3
|
import solara
|
|
4
|
-
from matplotlib.figure import Figure
|
|
5
|
-
from matplotlib.ticker import MaxNLocator
|
|
6
4
|
|
|
7
5
|
from mesa.examples.basic.virus_on_network.model import (
|
|
8
6
|
State,
|
|
9
7
|
VirusOnNetwork,
|
|
10
8
|
number_infected,
|
|
11
9
|
)
|
|
12
|
-
from mesa.visualization import
|
|
13
|
-
|
|
10
|
+
from mesa.visualization import (
|
|
11
|
+
Slider,
|
|
12
|
+
SolaraViz,
|
|
13
|
+
make_plot_component,
|
|
14
|
+
make_space_component,
|
|
15
|
+
)
|
|
14
16
|
|
|
15
|
-
def agent_portrayal(graph):
|
|
16
|
-
def get_agent(node):
|
|
17
|
-
return graph.nodes[node]["agent"][0]
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
edge_color = []
|
|
21
|
-
for u, v in graph.edges():
|
|
22
|
-
agent1 = get_agent(u)
|
|
23
|
-
agent2 = get_agent(v)
|
|
24
|
-
w = 2
|
|
25
|
-
ec = "#e8e8e8"
|
|
26
|
-
if State.RESISTANT in (agent1.state, agent2.state):
|
|
27
|
-
w = 3
|
|
28
|
-
ec = "black"
|
|
29
|
-
edge_width.append(w)
|
|
30
|
-
edge_color.append(ec)
|
|
18
|
+
def agent_portrayal(agent):
|
|
31
19
|
node_color_dict = {
|
|
32
20
|
State.INFECTED: "tab:red",
|
|
33
21
|
State.SUSCEPTIBLE: "tab:green",
|
|
34
22
|
State.RESISTANT: "tab:gray",
|
|
35
23
|
}
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
"width": edge_width,
|
|
39
|
-
"edge_color": edge_color,
|
|
40
|
-
"node_color": node_color,
|
|
41
|
-
}
|
|
24
|
+
return {"color": node_color_dict[agent.state], "size": 10}
|
|
42
25
|
|
|
43
26
|
|
|
44
27
|
def get_resistant_susceptible_ratio(model):
|
|
@@ -46,25 +29,9 @@ def get_resistant_susceptible_ratio(model):
|
|
|
46
29
|
ratio_text = r"$\infty$" if ratio is math.inf else f"{ratio:.2f}"
|
|
47
30
|
infected_text = str(number_infected(model))
|
|
48
31
|
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def make_plot(model):
|
|
53
|
-
# This is for the case when we want to plot multiple measures in 1 figure.
|
|
54
|
-
fig = Figure()
|
|
55
|
-
ax = fig.subplots()
|
|
56
|
-
measures = ["Infected", "Susceptible", "Resistant"]
|
|
57
|
-
colors = ["tab:red", "tab:green", "tab:gray"]
|
|
58
|
-
for i, m in enumerate(measures):
|
|
59
|
-
color = colors[i]
|
|
60
|
-
df = model.datacollector.get_model_vars_dataframe()
|
|
61
|
-
ax.plot(df.loc[:, m], label=m, color=color)
|
|
62
|
-
fig.legend()
|
|
63
|
-
# Set integer x axis
|
|
64
|
-
ax.xaxis.set_major_locator(MaxNLocator(integer=True))
|
|
65
|
-
ax.set_xlabel("Step")
|
|
66
|
-
ax.set_ylabel("Number of Agents")
|
|
67
|
-
return solara.FigureMatplotlib(fig)
|
|
32
|
+
return solara.Markdown(
|
|
33
|
+
f"Resistant/Susceptible Ratio: {ratio_text}<br>Infected Remaining: {infected_text}"
|
|
34
|
+
)
|
|
68
35
|
|
|
69
36
|
|
|
70
37
|
model_params = {
|
|
@@ -119,7 +86,18 @@ model_params = {
|
|
|
119
86
|
),
|
|
120
87
|
}
|
|
121
88
|
|
|
122
|
-
|
|
89
|
+
|
|
90
|
+
def post_process_lineplot(ax):
|
|
91
|
+
ax.set_ylim(ymin=0)
|
|
92
|
+
ax.set_ylabel("# people")
|
|
93
|
+
ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
SpacePlot = make_space_component(agent_portrayal)
|
|
97
|
+
StatePlot = make_plot_component(
|
|
98
|
+
{"Infected": "tab:red", "Susceptible": "tab:green", "Resistant": "tab:gray"},
|
|
99
|
+
post_process=post_process_lineplot,
|
|
100
|
+
)
|
|
123
101
|
|
|
124
102
|
model1 = VirusOnNetwork()
|
|
125
103
|
|
|
@@ -127,8 +105,8 @@ page = SolaraViz(
|
|
|
127
105
|
model1,
|
|
128
106
|
[
|
|
129
107
|
SpacePlot,
|
|
130
|
-
|
|
131
|
-
|
|
108
|
+
StatePlot,
|
|
109
|
+
get_resistant_susceptible_ratio,
|
|
132
110
|
],
|
|
133
111
|
model_params=model_params,
|
|
134
112
|
name="Virus Model",
|