Mesa 3.0.0rc0__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/examples/__init__.py +2 -2
- mesa/examples/advanced/pd_grid/app.py +1 -1
- mesa/examples/advanced/pd_grid/model.py +1 -1
- 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 +17 -6
- mesa/examples/advanced/wolf_sheep/model.py +68 -74
- mesa/examples/basic/boid_flockers/agents.py +49 -18
- 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 +8 -3
- mesa/examples/basic/boltzmann_wealth_model/model.py +48 -13
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +2 -2
- mesa/examples/basic/schelling/agents.py +9 -5
- mesa/examples/basic/schelling/model.py +48 -26
- 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/time.py +4 -4
- mesa/visualization/__init__.py +1 -1
- mesa/visualization/components/matplotlib_components.py +1 -2
- mesa/visualization/mpl_space_drawing.py +42 -7
- mesa/visualization/solara_viz.py +133 -54
- {mesa-3.0.0rc0.dist-info → mesa-3.0.1.dist-info}/METADATA +6 -8
- {mesa-3.0.0rc0.dist-info → mesa-3.0.1.dist-info}/RECORD +33 -33
- {mesa-3.0.0rc0.dist-info → mesa-3.0.1.dist-info}/WHEEL +1 -1
- /mesa/visualization/{UserParam.py → user_param.py} +0 -0
- {mesa-3.0.0rc0.dist-info → mesa-3.0.1.dist-info}/entry_points.txt +0 -0
- {mesa-3.0.0rc0.dist-info → mesa-3.0.1.dist-info}/licenses/LICENSE +0 -0
- {mesa-3.0.0rc0.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,4 +1,4 @@
|
|
|
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
4
|
make_plot_component,
|
|
@@ -20,6 +20,11 @@ model_params = {
|
|
|
20
20
|
"max": 100,
|
|
21
21
|
"step": 1,
|
|
22
22
|
},
|
|
23
|
+
"seed": {
|
|
24
|
+
"type": "InputText",
|
|
25
|
+
"value": 42,
|
|
26
|
+
"label": "Random Seed",
|
|
27
|
+
},
|
|
23
28
|
"width": 10,
|
|
24
29
|
"height": 10,
|
|
25
30
|
}
|
|
@@ -30,7 +35,7 @@ def post_process(ax):
|
|
|
30
35
|
|
|
31
36
|
|
|
32
37
|
# Create initial model instance
|
|
33
|
-
|
|
38
|
+
model = BoltzmannWealth(50, 10, 10)
|
|
34
39
|
|
|
35
40
|
# Create visualization elements. The visualization elements are solara components
|
|
36
41
|
# that receive the model instance as a "prop" and display it in a certain way.
|
|
@@ -49,7 +54,7 @@ GiniPlot = make_plot_component("Gini")
|
|
|
49
54
|
# solara run app.py
|
|
50
55
|
# It will automatically update and display any changes made to this file
|
|
51
56
|
page = SolaraViz(
|
|
52
|
-
|
|
57
|
+
model,
|
|
53
58
|
components=[SpaceGraph, GiniPlot],
|
|
54
59
|
model_params=model_params,
|
|
55
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,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:
|
|
@@ -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
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import itertools
|
|
6
|
+
import warnings
|
|
6
7
|
from collections.abc import Callable, Iterable, Mapping
|
|
7
8
|
from functools import cached_property
|
|
8
9
|
from random import Random
|
|
@@ -23,6 +24,12 @@ class CellCollection(Generic[T]):
|
|
|
23
24
|
agents (List[CellAgent]) : List of agents occupying the cells in this collection
|
|
24
25
|
random (Random) : The random number generator
|
|
25
26
|
|
|
27
|
+
Notes:
|
|
28
|
+
A `UserWarning` is issued if `random=None`. You can resolve this warning by explicitly
|
|
29
|
+
passing a random number generator. In most cases, this will be the seeded random number
|
|
30
|
+
generator in the model. So, you would do `random=self.random` in a `Model` or `Agent` instance.
|
|
31
|
+
|
|
32
|
+
|
|
26
33
|
"""
|
|
27
34
|
|
|
28
35
|
def __init__(
|
|
@@ -45,7 +52,12 @@ class CellCollection(Generic[T]):
|
|
|
45
52
|
self._capacity: int = next(iter(self._cells.keys())).capacity
|
|
46
53
|
|
|
47
54
|
if random is None:
|
|
48
|
-
|
|
55
|
+
warnings.warn(
|
|
56
|
+
"Random number generator not specified, this can make models non-reproducible. Please pass a random number generator explicitly",
|
|
57
|
+
UserWarning,
|
|
58
|
+
stacklevel=2,
|
|
59
|
+
)
|
|
60
|
+
random = Random()
|
|
49
61
|
self.random = random
|
|
50
62
|
|
|
51
63
|
def __iter__(self): # noqa
|
|
@@ -115,4 +127,4 @@ class CellCollection(Generic[T]):
|
|
|
115
127
|
yield cell
|
|
116
128
|
count += 1
|
|
117
129
|
|
|
118
|
-
return CellCollection(cell_generator(filter_func, at_most))
|
|
130
|
+
return CellCollection(cell_generator(filter_func, at_most), random=self.random)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import warnings
|
|
5
6
|
from collections.abc import Callable
|
|
6
7
|
from functools import cached_property
|
|
7
8
|
from random import Random
|
|
@@ -25,6 +26,12 @@ class DiscreteSpace(Generic[T]):
|
|
|
25
26
|
cell_klass (Type) : the type of cell class
|
|
26
27
|
empties (CellCollection) : collection of all cells that are empty
|
|
27
28
|
property_layers (dict[str, PropertyLayer]): the property layers of the discrete space
|
|
29
|
+
|
|
30
|
+
Notes:
|
|
31
|
+
A `UserWarning` is issued if `random=None`. You can resolve this warning by explicitly
|
|
32
|
+
passing a random number generator. In most cases, this will be the seeded random number
|
|
33
|
+
generator in the model. So, you would do `random=self.random` in a `Model` or `Agent` instance.
|
|
34
|
+
|
|
28
35
|
"""
|
|
29
36
|
|
|
30
37
|
def __init__(
|
|
@@ -44,7 +51,12 @@ class DiscreteSpace(Generic[T]):
|
|
|
44
51
|
self.capacity = capacity
|
|
45
52
|
self._cells: dict[tuple[int, ...], T] = {}
|
|
46
53
|
if random is None:
|
|
47
|
-
|
|
54
|
+
warnings.warn(
|
|
55
|
+
"Random number generator not specified, this can make models non-reproducible. Please pass a random number generator explicitly",
|
|
56
|
+
UserWarning,
|
|
57
|
+
stacklevel=2,
|
|
58
|
+
)
|
|
59
|
+
random = Random()
|
|
48
60
|
self.random = random
|
|
49
61
|
self.cell_klass = cell_klass
|
|
50
62
|
|
|
@@ -67,7 +79,9 @@ class DiscreteSpace(Generic[T]):
|
|
|
67
79
|
@cached_property
|
|
68
80
|
def all_cells(self):
|
|
69
81
|
"""Return all cells in space."""
|
|
70
|
-
return CellCollection(
|
|
82
|
+
return CellCollection(
|
|
83
|
+
{cell: cell.agents for cell in self._cells.values()}, random=self.random
|
|
84
|
+
)
|
|
71
85
|
|
|
72
86
|
def __iter__(self): # noqa
|
|
73
87
|
return iter(self._cells.values())
|