Mesa 3.1.2__py3-none-any.whl → 3.1.3__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/examples/basic/boid_flockers/agents.py +26 -38
- mesa/examples/basic/boid_flockers/app.py +6 -1
- mesa/examples/basic/boid_flockers/model.py +30 -37
- mesa/experimental/__init__.py +2 -2
- mesa/experimental/cell_space/voronoi.py +1 -4
- mesa/experimental/continuous_space/__init__.py +8 -0
- mesa/experimental/continuous_space/continuous_space.py +273 -0
- mesa/experimental/continuous_space/continuous_space_agents.py +101 -0
- mesa/model.py +1 -1
- mesa/space.py +7 -0
- mesa/visualization/mpl_space_drawing.py +22 -12
- mesa/visualization/solara_viz.py +30 -7
- {mesa-3.1.2.dist-info → mesa-3.1.3.dist-info}/METADATA +2 -1
- {mesa-3.1.2.dist-info → mesa-3.1.3.dist-info}/RECORD +19 -16
- {mesa-3.1.2.dist-info → mesa-3.1.3.dist-info}/WHEEL +0 -0
- {mesa-3.1.2.dist-info → mesa-3.1.3.dist-info}/entry_points.txt +0 -0
- {mesa-3.1.2.dist-info → mesa-3.1.3.dist-info}/licenses/LICENSE +0 -0
- {mesa-3.1.2.dist-info → mesa-3.1.3.dist-info}/licenses/NOTICE +0 -0
mesa/__init__.py
CHANGED
|
@@ -6,10 +6,10 @@ of flocking behavior.
|
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
|
|
9
|
-
from mesa import
|
|
9
|
+
from mesa.experimental.continuous_space import ContinuousSpaceAgent
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class Boid(
|
|
12
|
+
class Boid(ContinuousSpaceAgent):
|
|
13
13
|
"""A Boid-style flocker agent.
|
|
14
14
|
|
|
15
15
|
The agent follows three behaviors to flock:
|
|
@@ -26,10 +26,12 @@ class Boid(Agent):
|
|
|
26
26
|
def __init__(
|
|
27
27
|
self,
|
|
28
28
|
model,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
space,
|
|
30
|
+
position=(0, 0),
|
|
31
|
+
speed=1,
|
|
32
|
+
direction=(1, 1),
|
|
33
|
+
vision=1,
|
|
34
|
+
separation=1,
|
|
33
35
|
cohere=0.03,
|
|
34
36
|
separate=0.015,
|
|
35
37
|
match=0.05,
|
|
@@ -46,7 +48,8 @@ class Boid(Agent):
|
|
|
46
48
|
separate: Relative importance of avoiding close neighbors (default: 0.015)
|
|
47
49
|
match: Relative importance of matching neighbors' directions (default: 0.05)
|
|
48
50
|
"""
|
|
49
|
-
super().__init__(model)
|
|
51
|
+
super().__init__(space, model)
|
|
52
|
+
self.position = position
|
|
50
53
|
self.speed = speed
|
|
51
54
|
self.direction = direction
|
|
52
55
|
self.vision = vision
|
|
@@ -58,46 +61,31 @@ class Boid(Agent):
|
|
|
58
61
|
|
|
59
62
|
def step(self):
|
|
60
63
|
"""Get the Boid's neighbors, compute the new vector, and move accordingly."""
|
|
61
|
-
|
|
64
|
+
neighbors, distances = self.get_neighbors_in_radius(radius=self.vision)
|
|
65
|
+
self.neighbors = [n for n in neighbors if n is not self]
|
|
62
66
|
|
|
63
67
|
# If no neighbors, maintain current direction
|
|
64
|
-
if not
|
|
65
|
-
|
|
66
|
-
self.model.space.move_agent(self, new_pos)
|
|
68
|
+
if not neighbors:
|
|
69
|
+
self.position += self.direction * self.speed
|
|
67
70
|
return
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
cohere = np.zeros(2) # Cohesion vector
|
|
71
|
-
match_vector = np.zeros(2) # Alignment vector
|
|
72
|
-
separation_vector = np.zeros(2) # Separation vector
|
|
72
|
+
delta = self.space.calculate_difference_vector(self.position, agents=neighbors)
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# Separation - avoid getting too close
|
|
83
|
-
if distance < self.separation:
|
|
84
|
-
separation_vector -= heading
|
|
85
|
-
|
|
86
|
-
# Alignment - match neighbors' flying direction
|
|
87
|
-
match_vector += neighbor.direction
|
|
88
|
-
|
|
89
|
-
# Weight each behavior by its factor and normalize by number of neighbors
|
|
90
|
-
n = len(self.neighbors)
|
|
91
|
-
cohere = cohere * self.cohere_factor
|
|
92
|
-
separation_vector = separation_vector * self.separate_factor
|
|
93
|
-
match_vector = match_vector * self.match_factor
|
|
74
|
+
cohere_vector = delta.sum(axis=0) * self.cohere_factor
|
|
75
|
+
separation_vector = (
|
|
76
|
+
-1 * delta[distances < self.separation].sum(axis=0) * self.separate_factor
|
|
77
|
+
)
|
|
78
|
+
match_vector = (
|
|
79
|
+
np.asarray([n.direction for n in neighbors]).sum(axis=0) * self.match_factor
|
|
80
|
+
)
|
|
94
81
|
|
|
95
82
|
# Update direction based on the three behaviors
|
|
96
|
-
self.direction += (
|
|
83
|
+
self.direction += (cohere_vector + separation_vector + match_vector) / len(
|
|
84
|
+
neighbors
|
|
85
|
+
)
|
|
97
86
|
|
|
98
87
|
# Normalize direction vector
|
|
99
88
|
self.direction /= np.linalg.norm(self.direction)
|
|
100
89
|
|
|
101
90
|
# Move boid
|
|
102
|
-
|
|
103
|
-
self.model.space.move_agent(self, new_pos)
|
|
91
|
+
self.position += self.direction * self.speed
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
sys.path.insert(0, os.path.abspath("../../../.."))
|
|
5
|
+
|
|
1
6
|
from mesa.examples.basic.boid_flockers.model import BoidFlockers
|
|
2
7
|
from mesa.visualization import Slider, SolaraViz, make_space_component
|
|
3
8
|
|
|
@@ -17,7 +22,7 @@ model_params = {
|
|
|
17
22
|
"value": 42,
|
|
18
23
|
"label": "Random Seed",
|
|
19
24
|
},
|
|
20
|
-
"
|
|
25
|
+
"population_size": Slider(
|
|
21
26
|
label="Number of boids",
|
|
22
27
|
value=100,
|
|
23
28
|
min=10,
|
|
@@ -5,11 +5,17 @@ A Mesa implementation of Craig Reynolds's Boids flocker model.
|
|
|
5
5
|
Uses numpy arrays to represent vectors.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
sys.path.insert(0, os.path.abspath("../../../.."))
|
|
12
|
+
|
|
13
|
+
|
|
8
14
|
import numpy as np
|
|
9
15
|
|
|
10
16
|
from mesa import Model
|
|
11
17
|
from mesa.examples.basic.boid_flockers.agents import Boid
|
|
12
|
-
from mesa.
|
|
18
|
+
from mesa.experimental.continuous_space import ContinuousSpace
|
|
13
19
|
|
|
14
20
|
|
|
15
21
|
class BoidFlockers(Model):
|
|
@@ -17,7 +23,7 @@ class BoidFlockers(Model):
|
|
|
17
23
|
|
|
18
24
|
def __init__(
|
|
19
25
|
self,
|
|
20
|
-
|
|
26
|
+
population_size=100,
|
|
21
27
|
width=100,
|
|
22
28
|
height=100,
|
|
23
29
|
speed=1,
|
|
@@ -31,7 +37,7 @@ class BoidFlockers(Model):
|
|
|
31
37
|
"""Create a new Boids Flocking model.
|
|
32
38
|
|
|
33
39
|
Args:
|
|
34
|
-
|
|
40
|
+
population_size: Number of Boids in the simulation (default: 100)
|
|
35
41
|
width: Width of the space (default: 100)
|
|
36
42
|
height: Height of the space (default: 100)
|
|
37
43
|
speed: How fast the Boids move (default: 1)
|
|
@@ -44,48 +50,35 @@ class BoidFlockers(Model):
|
|
|
44
50
|
"""
|
|
45
51
|
super().__init__(seed=seed)
|
|
46
52
|
|
|
47
|
-
# Model Parameters
|
|
48
|
-
self.population = population
|
|
49
|
-
self.vision = vision
|
|
50
|
-
self.speed = speed
|
|
51
|
-
self.separation = separation
|
|
52
|
-
|
|
53
53
|
# Set up the space
|
|
54
|
-
self.space = ContinuousSpace(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
self.space = ContinuousSpace(
|
|
55
|
+
[[0, width], [0, height]],
|
|
56
|
+
torus=True,
|
|
57
|
+
random=self.random,
|
|
58
|
+
n_agents=population_size,
|
|
59
|
+
)
|
|
58
60
|
|
|
59
61
|
# Create and place the Boid agents
|
|
60
|
-
self.
|
|
62
|
+
positions = self.rng.random(size=(population_size, 2)) * self.space.size
|
|
63
|
+
directions = self.rng.uniform(-1, 1, size=(population_size, 2))
|
|
64
|
+
Boid.create_agents(
|
|
65
|
+
self,
|
|
66
|
+
population_size,
|
|
67
|
+
self.space,
|
|
68
|
+
position=positions,
|
|
69
|
+
direction=directions,
|
|
70
|
+
cohere=cohere,
|
|
71
|
+
separate=separate,
|
|
72
|
+
match=match,
|
|
73
|
+
speed=speed,
|
|
74
|
+
vision=vision,
|
|
75
|
+
separation=separation,
|
|
76
|
+
)
|
|
61
77
|
|
|
62
78
|
# For tracking statistics
|
|
63
79
|
self.average_heading = None
|
|
64
80
|
self.update_average_heading()
|
|
65
81
|
|
|
66
|
-
def make_agents(self):
|
|
67
|
-
"""Create and place all Boid agents randomly in the space."""
|
|
68
|
-
for _ in range(self.population):
|
|
69
|
-
# Random position
|
|
70
|
-
x = self.random.random() * self.space.x_max
|
|
71
|
-
y = self.random.random() * self.space.y_max
|
|
72
|
-
pos = np.array((x, y))
|
|
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
|
|
79
|
-
boid = Boid(
|
|
80
|
-
model=self,
|
|
81
|
-
speed=self.speed,
|
|
82
|
-
direction=direction,
|
|
83
|
-
vision=self.vision,
|
|
84
|
-
separation=self.separation,
|
|
85
|
-
**self.factors,
|
|
86
|
-
)
|
|
87
|
-
self.space.place_agent(boid, pos)
|
|
88
|
-
|
|
89
82
|
def update_average_heading(self):
|
|
90
83
|
"""Calculate the average heading (direction) of all Boids."""
|
|
91
84
|
if not self.agents:
|
mesa/experimental/__init__.py
CHANGED
|
@@ -15,6 +15,6 @@ Notes:
|
|
|
15
15
|
- Features graduate from experimental status once their APIs are stabilized
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
from mesa.experimental import cell_space, devs, mesa_signals
|
|
18
|
+
from mesa.experimental import cell_space, continuous_space, devs, mesa_signals
|
|
19
19
|
|
|
20
|
-
__all__ = ["cell_space", "devs", "mesa_signals"]
|
|
20
|
+
__all__ = ["cell_space", "continuous_space", "devs", "mesa_signals"]
|
|
@@ -186,7 +186,6 @@ class VoronoiGrid(DiscreteSpace):
|
|
|
186
186
|
random: Random | None = None,
|
|
187
187
|
cell_klass: type[Cell] = Cell,
|
|
188
188
|
capacity_function: callable = round_float,
|
|
189
|
-
cell_coloring_property: str | None = None,
|
|
190
189
|
) -> None:
|
|
191
190
|
"""A Voronoi Tessellation Grid.
|
|
192
191
|
|
|
@@ -200,7 +199,7 @@ class VoronoiGrid(DiscreteSpace):
|
|
|
200
199
|
random (Random): random number generator
|
|
201
200
|
cell_klass (type[Cell]): type of cell class
|
|
202
201
|
capacity_function (Callable): function to compute (int) capacity according to (float) area
|
|
203
|
-
|
|
202
|
+
|
|
204
203
|
"""
|
|
205
204
|
super().__init__(capacity=capacity, random=random, cell_klass=cell_klass)
|
|
206
205
|
self.centroids_coordinates = centroids_coordinates
|
|
@@ -215,7 +214,6 @@ class VoronoiGrid(DiscreteSpace):
|
|
|
215
214
|
self.triangulation = None
|
|
216
215
|
self.voronoi_coordinates = None
|
|
217
216
|
self.capacity_function = capacity_function
|
|
218
|
-
self.cell_coloring_property = cell_coloring_property
|
|
219
217
|
|
|
220
218
|
self._connect_cells()
|
|
221
219
|
self._build_cell_polygons()
|
|
@@ -266,4 +264,3 @@ class VoronoiGrid(DiscreteSpace):
|
|
|
266
264
|
polygon_area = self._compute_polygon_area(polygon)
|
|
267
265
|
self._cells[region].properties["area"] = polygon_area
|
|
268
266
|
self._cells[region].capacity = self.capacity_function(polygon_area)
|
|
269
|
-
self._cells[region].properties[self.cell_coloring_property] = 0
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Continuous space support."""
|
|
2
|
+
|
|
3
|
+
from mesa.experimental.continuous_space.continuous_space import ContinuousSpace
|
|
4
|
+
from mesa.experimental.continuous_space.continuous_space_agents import (
|
|
5
|
+
ContinuousSpaceAgent,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
__all__ = ["ContinuousSpace", "ContinuousSpaceAgent"]
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""A Continuous Space class."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
from collections.abc import Iterable
|
|
5
|
+
from itertools import compress
|
|
6
|
+
from random import Random
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from numpy.typing import ArrayLike
|
|
10
|
+
from scipy.spatial.distance import cdist
|
|
11
|
+
|
|
12
|
+
from mesa.agent import Agent, AgentSet
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ContinuousSpace:
|
|
16
|
+
"""Continuous space where each agent can have an arbitrary position."""
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def x_min(self): # noqa: D102
|
|
20
|
+
# compatibility with solara_viz
|
|
21
|
+
return self.dimensions[0, 0]
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def x_max(self): # noqa: D102
|
|
25
|
+
# compatibility with solara_viz
|
|
26
|
+
return self.dimensions[0, 1]
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def y_min(self): # noqa: D102
|
|
30
|
+
# compatibility with solara_viz
|
|
31
|
+
return self.dimensions[1, 0]
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def y_max(self): # noqa: D102
|
|
35
|
+
# compatibility with solara_viz
|
|
36
|
+
return self.dimensions[1, 1]
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def width(self): # noqa: D102
|
|
40
|
+
# compatibility with solara_viz
|
|
41
|
+
return self.size[0]
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def height(self): # noqa: D102
|
|
45
|
+
# compatibility with solara_viz
|
|
46
|
+
return self.size[1]
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
dimensions: ArrayLike,
|
|
51
|
+
torus: bool = False,
|
|
52
|
+
random: Random | None = None,
|
|
53
|
+
n_agents: int = 100,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Create a new continuous space.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
dimensions: a numpy array like object where each row specifies the minimum and maximum value of that dimension.
|
|
59
|
+
torus: boolean for whether the space wraps around or not
|
|
60
|
+
random: a seeded stdlib random.Random instance
|
|
61
|
+
n_agents: the expected number of agents in the space
|
|
62
|
+
|
|
63
|
+
Internally, a numpy array is used to store the positions of all agents. This is resized if needed,
|
|
64
|
+
but you can control the initial size explicitly by passing n_agents.
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
if random is None:
|
|
69
|
+
warnings.warn(
|
|
70
|
+
"Random number generator not specified, this can make models non-reproducible. Please pass a random number generator explicitly",
|
|
71
|
+
UserWarning,
|
|
72
|
+
stacklevel=2,
|
|
73
|
+
)
|
|
74
|
+
random = Random()
|
|
75
|
+
self.random = random
|
|
76
|
+
|
|
77
|
+
self.dimensions: np.array = np.asanyarray(dimensions)
|
|
78
|
+
self.ndims: int = self.dimensions.shape[0]
|
|
79
|
+
self.size: np.array = self.dimensions[:, 1] - self.dimensions[:, 0]
|
|
80
|
+
self.center: np.array = np.sum(self.dimensions, axis=1) / 2
|
|
81
|
+
|
|
82
|
+
self.torus: bool = torus
|
|
83
|
+
|
|
84
|
+
# self._agent_positions is the array containing all agent positions
|
|
85
|
+
# plus potential extra empty rows
|
|
86
|
+
# agent_positions is a view into _agent_positions containing only the filled rows
|
|
87
|
+
self._agent_positions: np.array = np.empty(
|
|
88
|
+
(n_agents, self.dimensions.shape[0]), dtype=float
|
|
89
|
+
)
|
|
90
|
+
self.agent_positions: (
|
|
91
|
+
np.array
|
|
92
|
+
) # a view on _agent_positions containing all active positions
|
|
93
|
+
|
|
94
|
+
# the list of agents in the space
|
|
95
|
+
self.active_agents = []
|
|
96
|
+
self._n_agents = 0 # the number of active agents in the space
|
|
97
|
+
|
|
98
|
+
# a mapping from agents to index and vice versa
|
|
99
|
+
self._index_to_agent: dict[int, Agent] = {}
|
|
100
|
+
self._agent_to_index: dict[Agent, int | None] = {}
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def agents(self) -> AgentSet:
|
|
104
|
+
"""Return an AgentSet with the agents in the space."""
|
|
105
|
+
return AgentSet(self.active_agents, random=self.random)
|
|
106
|
+
|
|
107
|
+
def _add_agent(self, agent: Agent) -> int:
|
|
108
|
+
"""Helper method for adding an agent to the space.
|
|
109
|
+
|
|
110
|
+
This method manages the numpy array with the agent positions and ensuring it is
|
|
111
|
+
enlarged if and when needed. It is called automatically by ContinousSpaceAgent when created.
|
|
112
|
+
|
|
113
|
+
"""
|
|
114
|
+
index = self._n_agents
|
|
115
|
+
self._n_agents += 1
|
|
116
|
+
|
|
117
|
+
if self._agent_positions.shape[0] <= index:
|
|
118
|
+
# we are out of space
|
|
119
|
+
fraction = 0.2 # we add 20% Fixme
|
|
120
|
+
n = int(round(fraction * self._n_agents))
|
|
121
|
+
self._agent_positions = np.vstack(
|
|
122
|
+
[
|
|
123
|
+
self._agent_positions,
|
|
124
|
+
np.empty(
|
|
125
|
+
(n, self.dimensions.shape[0]),
|
|
126
|
+
),
|
|
127
|
+
]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
self._agent_to_index[agent] = index
|
|
131
|
+
self._index_to_agent[index] = agent
|
|
132
|
+
|
|
133
|
+
# we want to maintain a view rather than a copy on the active agents and positions
|
|
134
|
+
# this is essential for the performance of the rest of this code
|
|
135
|
+
self.active_agents.append(agent)
|
|
136
|
+
self.agent_positions = self._agent_positions[0 : self._n_agents]
|
|
137
|
+
|
|
138
|
+
return index
|
|
139
|
+
|
|
140
|
+
def _remove_agent(self, agent: Agent) -> None:
|
|
141
|
+
"""Remove an agent from the space.
|
|
142
|
+
|
|
143
|
+
This method is automatically called by ContinuousSpaceAgent.remove.
|
|
144
|
+
|
|
145
|
+
"""
|
|
146
|
+
index = self._agent_to_index[agent]
|
|
147
|
+
self._agent_to_index.pop(agent, None)
|
|
148
|
+
self._index_to_agent.pop(index, None)
|
|
149
|
+
del self.active_agents[index]
|
|
150
|
+
|
|
151
|
+
# we update all indices
|
|
152
|
+
for agent in self.active_agents[index::]:
|
|
153
|
+
old_index = self._agent_to_index[agent]
|
|
154
|
+
self._agent_to_index[agent] = old_index - 1
|
|
155
|
+
self._index_to_agent[old_index - 1] = agent
|
|
156
|
+
|
|
157
|
+
# we move all data below the removed agent one row up
|
|
158
|
+
self._agent_positions[index : self._n_agents - 1] = self._agent_positions[
|
|
159
|
+
index + 1 : self._n_agents
|
|
160
|
+
]
|
|
161
|
+
self._n_agents -= 1
|
|
162
|
+
self.agent_positions = self._agent_positions[0 : self._n_agents]
|
|
163
|
+
|
|
164
|
+
def calculate_difference_vector(self, point: np.ndarray, agents=None) -> np.ndarray:
|
|
165
|
+
"""Calculate the difference vector between the point and all agenents.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
point: the point to calculate the difference vector for
|
|
169
|
+
agents: the agents to calculate the difference vector of point with. By default,
|
|
170
|
+
all agents are considered.
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
"""
|
|
174
|
+
point = np.asanyarray(point)
|
|
175
|
+
positions = (
|
|
176
|
+
self.agent_positions
|
|
177
|
+
if agents is None
|
|
178
|
+
else self._agent_positions[[self._agent_to_index[a] for a in agents]]
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
delta = positions - point
|
|
182
|
+
|
|
183
|
+
if self.torus:
|
|
184
|
+
inverse_delta = delta - np.sign(delta) * self.size
|
|
185
|
+
|
|
186
|
+
# we need to use the lowest absolute value from delta and inverse delta
|
|
187
|
+
logical = np.abs(delta) < np.abs(inverse_delta)
|
|
188
|
+
|
|
189
|
+
out = np.zeros(delta.shape)
|
|
190
|
+
out[logical] = delta[logical]
|
|
191
|
+
out[~logical] = inverse_delta[~logical]
|
|
192
|
+
|
|
193
|
+
delta = out
|
|
194
|
+
|
|
195
|
+
return delta
|
|
196
|
+
|
|
197
|
+
def calculate_distances(
|
|
198
|
+
self, point: ArrayLike, agents: Iterable[Agent] | None = None, **kwargs
|
|
199
|
+
) -> tuple[np.ndarray, list]:
|
|
200
|
+
"""Calculate the distance between the point and all agents.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
point: the point to calculate the difference vector for
|
|
204
|
+
agents: the agents to calculate the difference vector of point with. By default,
|
|
205
|
+
all agents are considered.
|
|
206
|
+
kwargs: any additional keyword arguments are passed to scipy's cdist, which is used
|
|
207
|
+
only if torus is False. This allows for non-Euclidian distance measures.
|
|
208
|
+
|
|
209
|
+
"""
|
|
210
|
+
point = np.asanyarray(point)
|
|
211
|
+
|
|
212
|
+
if agents is None:
|
|
213
|
+
positions = self.agent_positions
|
|
214
|
+
agents = self.active_agents
|
|
215
|
+
else:
|
|
216
|
+
positions = self._agent_positions[[self._agent_to_index[a] for a in agents]]
|
|
217
|
+
agents = np.asarray(agents)
|
|
218
|
+
|
|
219
|
+
if self.torus:
|
|
220
|
+
delta = np.abs(point - positions)
|
|
221
|
+
delta = np.minimum(delta, self.size - delta, out=delta)
|
|
222
|
+
|
|
223
|
+
# + is much faster than np.sum or array.sum
|
|
224
|
+
dists = delta[:, 0] ** 2
|
|
225
|
+
for i in range(1, self.ndims):
|
|
226
|
+
dists += delta[:, i] ** 2
|
|
227
|
+
dists = np.sqrt(dists)
|
|
228
|
+
else:
|
|
229
|
+
dists = cdist(point[np.newaxis, :], positions, **kwargs)[0, :]
|
|
230
|
+
return dists, agents
|
|
231
|
+
|
|
232
|
+
def get_agents_in_radius(
|
|
233
|
+
self, point: ArrayLike, radius: float | int = 1
|
|
234
|
+
) -> tuple[list, np.ndarray]:
|
|
235
|
+
"""Return the agents and their distances within a radius for the point."""
|
|
236
|
+
distances, agents = self.calculate_distances(point)
|
|
237
|
+
logical = distances <= radius
|
|
238
|
+
agents = list(compress(agents, logical))
|
|
239
|
+
return (
|
|
240
|
+
agents,
|
|
241
|
+
distances[logical],
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
def get_k_nearest_agents(
|
|
245
|
+
self, point: ArrayLike, k: int = 1
|
|
246
|
+
) -> tuple[list, np.ndarray]:
|
|
247
|
+
"""Return the k nearest agents and their distances to the point.
|
|
248
|
+
|
|
249
|
+
Notes:
|
|
250
|
+
This method returns exactly k agents, ignoring ties. In case of ties, the
|
|
251
|
+
earlier an agent is inserted the higher it will rank.
|
|
252
|
+
|
|
253
|
+
"""
|
|
254
|
+
dists, agents = self.calculate_distances(point)
|
|
255
|
+
|
|
256
|
+
indices = np.argpartition(dists, k)[:k]
|
|
257
|
+
agents = [agents[i] for i in indices]
|
|
258
|
+
return agents, dists[indices]
|
|
259
|
+
|
|
260
|
+
def in_bounds(self, point: ArrayLike) -> bool:
|
|
261
|
+
"""Check if point is inside the bounds of the space."""
|
|
262
|
+
return bool(
|
|
263
|
+
(
|
|
264
|
+
(np.asanyarray(point) >= self.dimensions[:, 0])
|
|
265
|
+
& (point <= self.dimensions[:, 1])
|
|
266
|
+
).all()
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def torus_correct(self, point: ArrayLike) -> np.ndarray:
|
|
270
|
+
"""Apply a torus correction to the point."""
|
|
271
|
+
return self.dimensions[:, 0] + np.mod(
|
|
272
|
+
np.asanyarray(point) - self.dimensions[:, 0], self.size
|
|
273
|
+
)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Continuous space agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from itertools import compress
|
|
6
|
+
from typing import Protocol
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from mesa.agent import Agent
|
|
11
|
+
from mesa.experimental.continuous_space import ContinuousSpace
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HasPositionProtocol(Protocol):
|
|
15
|
+
"""Protocol for continuous space position holders."""
|
|
16
|
+
|
|
17
|
+
position: np.ndarray
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ContinuousSpaceAgent(Agent):
|
|
21
|
+
"""A continuous space agent.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
space (ContinuousSpace): the continuous space in which the agent is located
|
|
25
|
+
position (np.ndarray): the position of the agent
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
__slots__ = ["_mesa_index", "space"]
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def position(self) -> np.ndarray:
|
|
33
|
+
"""Position of the agent."""
|
|
34
|
+
return self.space.agent_positions[self.space._agent_to_index[self]]
|
|
35
|
+
|
|
36
|
+
@position.setter
|
|
37
|
+
def position(self, value: np.ndarray) -> None:
|
|
38
|
+
if not self.space.in_bounds(value):
|
|
39
|
+
if self.space.torus:
|
|
40
|
+
value = self.space.torus_correct(value)
|
|
41
|
+
else:
|
|
42
|
+
raise ValueError(f"point {value} is outside the bounds of the space")
|
|
43
|
+
|
|
44
|
+
self.space.agent_positions[self.space._agent_to_index[self]] = value
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def pos(self): # noqa: D102
|
|
48
|
+
# just here for compatibility with solara_viz.
|
|
49
|
+
return self.position
|
|
50
|
+
|
|
51
|
+
@pos.setter
|
|
52
|
+
def pos(self, value):
|
|
53
|
+
# just here for compatibility solara_viz.
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
def __init__(self, space: ContinuousSpace, model):
|
|
57
|
+
"""Initialize a continuous space agent.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
space: the continuous space in which the agent is located
|
|
61
|
+
model: the model to which the agent belongs
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
super().__init__(model)
|
|
65
|
+
self.space: ContinuousSpace = space
|
|
66
|
+
self.space._add_agent(self)
|
|
67
|
+
# self.position[:] = np.nan
|
|
68
|
+
|
|
69
|
+
def remove(self) -> None:
|
|
70
|
+
"""Remove and delete the agent from the model and continuous space."""
|
|
71
|
+
super().remove()
|
|
72
|
+
self.space._remove_agent(self)
|
|
73
|
+
self._mesa_index = None
|
|
74
|
+
self.space = None
|
|
75
|
+
|
|
76
|
+
def get_neighbors_in_radius(
|
|
77
|
+
self, radius: float | int = 1
|
|
78
|
+
) -> tuple[list, np.ndarray]:
|
|
79
|
+
"""Get neighbors within radius.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
radius: radius within which to look for neighbors
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
agents, dists = self.space.get_agents_in_radius(self.position, radius=radius)
|
|
86
|
+
logical = np.asarray([agent is not self for agent in agents])
|
|
87
|
+
agents = list(compress(agents, logical))
|
|
88
|
+
return agents, dists[logical]
|
|
89
|
+
|
|
90
|
+
def get_nearest_neighbors(self, k: int = 1) -> tuple[list, np.ndarray]:
|
|
91
|
+
"""Get neighbors within radius.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
k: the number of nearest neighbors to return
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
# return includes self, so we need to get k+1
|
|
98
|
+
agents, dists = self.space.get_k_nearest_agents(self.position, k=k + 1)
|
|
99
|
+
logical = np.asarray([agent is not self for agent in agents])
|
|
100
|
+
agents = list(compress(agents, logical))
|
|
101
|
+
return agents, dists[logical]
|
mesa/model.py
CHANGED
|
@@ -94,7 +94,7 @@ class Model:
|
|
|
94
94
|
self._seed = seed # this allows for reproducing stdlib.random
|
|
95
95
|
|
|
96
96
|
try:
|
|
97
|
-
self.rng: np.random.Generator = np.random.default_rng(
|
|
97
|
+
self.rng: np.random.Generator = np.random.default_rng(seed)
|
|
98
98
|
except TypeError:
|
|
99
99
|
rng = self.random.randint(0, sys.maxsize)
|
|
100
100
|
self.rng: np.random.Generator = np.random.default_rng(rng)
|
mesa/space.py
CHANGED
|
@@ -1415,6 +1415,13 @@ class ContinuousSpace:
|
|
|
1415
1415
|
coordinates. i.e. if you are searching for the
|
|
1416
1416
|
neighbors of a given agent, True will include that
|
|
1417
1417
|
agent in the results.
|
|
1418
|
+
|
|
1419
|
+
Notes:
|
|
1420
|
+
If 1 or more agents are located on pos, include_center=False will remove all these agents
|
|
1421
|
+
from the results. So, if you really want to get the neighbors of a given agent,
|
|
1422
|
+
you should set include_center=True, and then filter the list of agents to remove
|
|
1423
|
+
the given agent (i.e., self when calling it from an agent).
|
|
1424
|
+
|
|
1418
1425
|
"""
|
|
1419
1426
|
if self._agent_points is None:
|
|
1420
1427
|
self._build_agent_cache()
|
|
@@ -20,7 +20,7 @@ from matplotlib.axes import Axes
|
|
|
20
20
|
from matplotlib.cm import ScalarMappable
|
|
21
21
|
from matplotlib.collections import PatchCollection
|
|
22
22
|
from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba
|
|
23
|
-
from matplotlib.patches import RegularPolygon
|
|
23
|
+
from matplotlib.patches import Polygon, RegularPolygon
|
|
24
24
|
|
|
25
25
|
import mesa
|
|
26
26
|
from mesa.experimental.cell_space import (
|
|
@@ -143,7 +143,10 @@ def draw_space(
|
|
|
143
143
|
draw_orthogonal_grid(space, agent_portrayal, ax=ax, **space_drawing_kwargs)
|
|
144
144
|
case mesa.space.NetworkGrid() | mesa.experimental.cell_space.Network():
|
|
145
145
|
draw_network(space, agent_portrayal, ax=ax, **space_drawing_kwargs)
|
|
146
|
-
case
|
|
146
|
+
case (
|
|
147
|
+
mesa.space.ContinuousSpace()
|
|
148
|
+
| mesa.experimental.continuous_space.ContinuousSpace()
|
|
149
|
+
):
|
|
147
150
|
draw_continuous_space(space, agent_portrayal, ax=ax)
|
|
148
151
|
case VoronoiGrid():
|
|
149
152
|
draw_voronoi_grid(space, agent_portrayal, ax=ax)
|
|
@@ -498,7 +501,11 @@ def draw_continuous_space(
|
|
|
498
501
|
|
|
499
502
|
|
|
500
503
|
def draw_voronoi_grid(
|
|
501
|
-
space: VoronoiGrid,
|
|
504
|
+
space: VoronoiGrid,
|
|
505
|
+
agent_portrayal: Callable,
|
|
506
|
+
ax: Axes | None = None,
|
|
507
|
+
draw_grid: bool = True,
|
|
508
|
+
**kwargs,
|
|
502
509
|
):
|
|
503
510
|
"""Visualize a voronoi grid.
|
|
504
511
|
|
|
@@ -506,6 +513,7 @@ def draw_voronoi_grid(
|
|
|
506
513
|
space: the space to visualize
|
|
507
514
|
agent_portrayal: a callable that is called with the agent and returns a dict
|
|
508
515
|
ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots
|
|
516
|
+
draw_grid: whether to draw the grid or not
|
|
509
517
|
kwargs: additional keyword arguments passed to ax.scatter
|
|
510
518
|
|
|
511
519
|
Returns:
|
|
@@ -538,16 +546,18 @@ def draw_voronoi_grid(
|
|
|
538
546
|
|
|
539
547
|
_scatter(ax, arguments, **kwargs)
|
|
540
548
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
)
|
|
549
|
-
|
|
549
|
+
def setup_voroinoimesh(cells):
|
|
550
|
+
patches = []
|
|
551
|
+
for cell in cells:
|
|
552
|
+
patch = Polygon(cell.properties["polygon"])
|
|
553
|
+
patches.append(patch)
|
|
554
|
+
mesh = PatchCollection(
|
|
555
|
+
patches, edgecolor="k", facecolor=(1, 1, 1, 0), linestyle="dotted", lw=1
|
|
556
|
+
)
|
|
557
|
+
return mesh
|
|
550
558
|
|
|
559
|
+
if draw_grid:
|
|
560
|
+
ax.add_collection(setup_voroinoimesh(space.all_cells.cells))
|
|
551
561
|
return ax
|
|
552
562
|
|
|
553
563
|
|
mesa/visualization/solara_viz.py
CHANGED
|
@@ -52,6 +52,7 @@ def SolaraViz(
|
|
|
52
52
|
| Literal["default"] = "default",
|
|
53
53
|
*,
|
|
54
54
|
play_interval: int = 100,
|
|
55
|
+
render_interval: int = 1,
|
|
55
56
|
simulator: Simulator | None = None,
|
|
56
57
|
model_params=None,
|
|
57
58
|
name: str | None = None,
|
|
@@ -72,6 +73,8 @@ def SolaraViz(
|
|
|
72
73
|
Defaults to "default", which uses the default Altair space visualization.
|
|
73
74
|
play_interval (int, optional): Interval for playing the model steps in milliseconds.
|
|
74
75
|
This controls the speed of the model's automatic stepping. Defaults to 100 ms.
|
|
76
|
+
render_interval (int, optional): Controls how often plots are updated during a simulation,
|
|
77
|
+
allowing users to skip intermediate steps and update graphs less frequently.
|
|
75
78
|
simulator: A simulator that controls the model (optional)
|
|
76
79
|
model_params (dict, optional): Parameters for (re-)instantiating a model.
|
|
77
80
|
Can include user-adjustable parameters and fixed parameters. Defaults to None.
|
|
@@ -90,6 +93,8 @@ def SolaraViz(
|
|
|
90
93
|
model instance is provided, it will be converted to a reactive model using `solara.use_reactive`.
|
|
91
94
|
- The `play_interval` argument controls the speed of the model's automatic stepping. A lower
|
|
92
95
|
value results in faster stepping, while a higher value results in slower stepping.
|
|
96
|
+
- The `render_interval` argument determines how often plots are updated during simulation. Higher values
|
|
97
|
+
reduce update frequency,resulting in faster execution.
|
|
93
98
|
"""
|
|
94
99
|
if components == "default":
|
|
95
100
|
components = [components_altair.make_altair_space()]
|
|
@@ -103,7 +108,7 @@ def SolaraViz(
|
|
|
103
108
|
# set up reactive model_parameters shared by ModelCreator and ModelController
|
|
104
109
|
reactive_model_parameters = solara.use_reactive({})
|
|
105
110
|
reactive_play_interval = solara.use_reactive(play_interval)
|
|
106
|
-
|
|
111
|
+
reactive_render_interval = solara.use_reactive(render_interval)
|
|
107
112
|
with solara.AppBar():
|
|
108
113
|
solara.AppBarTitle(name if name else model.value.__class__.__name__)
|
|
109
114
|
|
|
@@ -117,11 +122,20 @@ def SolaraViz(
|
|
|
117
122
|
max=500,
|
|
118
123
|
step=10,
|
|
119
124
|
)
|
|
125
|
+
solara.SliderInt(
|
|
126
|
+
label="Render Interval (steps)",
|
|
127
|
+
value=reactive_render_interval,
|
|
128
|
+
on_value=lambda v: reactive_render_interval.set(v),
|
|
129
|
+
min=1,
|
|
130
|
+
max=100,
|
|
131
|
+
step=2,
|
|
132
|
+
)
|
|
120
133
|
if not isinstance(simulator, Simulator):
|
|
121
134
|
ModelController(
|
|
122
135
|
model,
|
|
123
136
|
model_parameters=reactive_model_parameters,
|
|
124
137
|
play_interval=reactive_play_interval,
|
|
138
|
+
render_interval=reactive_render_interval,
|
|
125
139
|
)
|
|
126
140
|
else:
|
|
127
141
|
SimulatorController(
|
|
@@ -129,6 +143,7 @@ def SolaraViz(
|
|
|
129
143
|
simulator,
|
|
130
144
|
model_parameters=reactive_model_parameters,
|
|
131
145
|
play_interval=reactive_play_interval,
|
|
146
|
+
render_interval=reactive_render_interval,
|
|
132
147
|
)
|
|
133
148
|
with solara.Card("Model Parameters"):
|
|
134
149
|
ModelCreator(
|
|
@@ -189,6 +204,7 @@ def ModelController(
|
|
|
189
204
|
*,
|
|
190
205
|
model_parameters: dict | solara.Reactive[dict] = None,
|
|
191
206
|
play_interval: int | solara.Reactive[int] = 100,
|
|
207
|
+
render_interval: int | solara.Reactive[int] = 1,
|
|
192
208
|
):
|
|
193
209
|
"""Create controls for model execution (step, play, pause, reset).
|
|
194
210
|
|
|
@@ -196,7 +212,7 @@ def ModelController(
|
|
|
196
212
|
model: Reactive model instance
|
|
197
213
|
model_parameters: Reactive parameters for (re-)instantiating a model.
|
|
198
214
|
play_interval: Interval for playing the model steps in milliseconds.
|
|
199
|
-
|
|
215
|
+
render_interval: Controls how often the plots are updated during simulation steps.Higher value reduce update frequency.
|
|
200
216
|
"""
|
|
201
217
|
playing = solara.use_reactive(False)
|
|
202
218
|
running = solara.use_reactive(True)
|
|
@@ -215,9 +231,12 @@ def ModelController(
|
|
|
215
231
|
|
|
216
232
|
@function_logger(__name__)
|
|
217
233
|
def do_step():
|
|
218
|
-
"""Advance the model by
|
|
219
|
-
|
|
234
|
+
"""Advance the model by the number of steps specified by the render_interval slider."""
|
|
235
|
+
for _ in range(render_interval.value):
|
|
236
|
+
model.value.step()
|
|
237
|
+
|
|
220
238
|
running.value = model.value.running
|
|
239
|
+
|
|
221
240
|
force_update()
|
|
222
241
|
|
|
223
242
|
@function_logger(__name__)
|
|
@@ -259,6 +278,7 @@ def SimulatorController(
|
|
|
259
278
|
*,
|
|
260
279
|
model_parameters: dict | solara.Reactive[dict] = None,
|
|
261
280
|
play_interval: int | solara.Reactive[int] = 100,
|
|
281
|
+
render_interval: int | solara.Reactive[int] = 1,
|
|
262
282
|
):
|
|
263
283
|
"""Create controls for model execution (step, play, pause, reset).
|
|
264
284
|
|
|
@@ -267,7 +287,11 @@ def SimulatorController(
|
|
|
267
287
|
simulator: Simulator instance
|
|
268
288
|
model_parameters: Reactive parameters for (re-)instantiating a model.
|
|
269
289
|
play_interval: Interval for playing the model steps in milliseconds.
|
|
290
|
+
render_interval: Controls how often the plots are updated during simulation steps.Higher values reduce update frequency.
|
|
270
291
|
|
|
292
|
+
Notes:
|
|
293
|
+
The `step button` increments the step by the value specified in the `render_interval` slider.
|
|
294
|
+
This behavior ensures synchronization between simulation steps and plot updates.
|
|
271
295
|
"""
|
|
272
296
|
playing = solara.use_reactive(False)
|
|
273
297
|
running = solara.use_reactive(True)
|
|
@@ -285,8 +309,8 @@ def SimulatorController(
|
|
|
285
309
|
)
|
|
286
310
|
|
|
287
311
|
def do_step():
|
|
288
|
-
"""Advance the model by
|
|
289
|
-
simulator.run_for(
|
|
312
|
+
"""Advance the model by the number of steps specified by the render_interval slider."""
|
|
313
|
+
simulator.run_for(render_interval.value)
|
|
290
314
|
running.value = model.value.running
|
|
291
315
|
force_update()
|
|
292
316
|
|
|
@@ -390,7 +414,6 @@ def ModelCreator(
|
|
|
390
414
|
or are dictionaries containing parameter details such as type, value, min, and max.
|
|
391
415
|
- The `seed` argument ensures reproducibility by setting the initial seed for the model's random number generator.
|
|
392
416
|
- The component provides an interface for adjusting user-defined parameters and reseeding the model.
|
|
393
|
-
|
|
394
417
|
"""
|
|
395
418
|
if model_parameters is None:
|
|
396
419
|
model_parameters = {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Mesa
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.3
|
|
4
4
|
Summary: Agent-based modeling (ABM) in Python
|
|
5
5
|
Project-URL: homepage, https://github.com/projectmesa/mesa
|
|
6
6
|
Project-URL: repository, https://github.com/projectmesa/mesa
|
|
@@ -24,6 +24,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Life
|
|
|
24
24
|
Requires-Python: >=3.11
|
|
25
25
|
Requires-Dist: numpy
|
|
26
26
|
Requires-Dist: pandas
|
|
27
|
+
Requires-Dist: scipy
|
|
27
28
|
Requires-Dist: tqdm
|
|
28
29
|
Provides-Extra: all
|
|
29
30
|
Requires-Dist: ipython; extra == 'all'
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
mesa/__init__.py,sha256=
|
|
1
|
+
mesa/__init__.py,sha256=cQpR402xBpOx0vJjUbQB8d6D9d2JK_ZpWkmWSlm4cWk,611
|
|
2
2
|
mesa/agent.py,sha256=4CXMOFA9KhvTypaV_OHZGqxOR4GVwyX4x8DOtQENUQA,26130
|
|
3
3
|
mesa/batchrunner.py,sha256=w8StV82F_7DAAVQc5V7_Ggp0EL1NYn__UcBE-Nwrgv4,7771
|
|
4
4
|
mesa/datacollection.py,sha256=8loT4pQsXcHArxHSsbRc7HTc2GP5gsEIeKFKr3xya4I,15991
|
|
5
5
|
mesa/mesa_logging.py,sha256=PEDqUaQ2Y4bkYBkrHVkGT0sF86gUdbSH1T3vCg3qQeE,4949
|
|
6
|
-
mesa/model.py,sha256=
|
|
7
|
-
mesa/space.py,sha256=
|
|
6
|
+
mesa/model.py,sha256=VkdBea_mkWcBMxMq-pwuU23UlI1gbG4TOIyqkBfeiFo,8354
|
|
7
|
+
mesa/space.py,sha256=MNCblKf862pdkoIAa-VpjaurmI8GJtb02W3q3QWFjTE,64458
|
|
8
8
|
mesa/examples/README.md,sha256=dNn8kv0BNQem3NNhO5mbOANQoK8UUYOo7rnkCFV9tnE,2882
|
|
9
9
|
mesa/examples/__init__.py,sha256=pyPWFRUxyYtQilJECbH7LY1eYBk8VB0Yg-_SbFEEvFA,825
|
|
10
10
|
mesa/examples/advanced/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -35,9 +35,9 @@ mesa/examples/advanced/wolf_sheep/model.py,sha256=IUN1STm6jCGuzXo2sCF86r1U-dI63y
|
|
|
35
35
|
mesa/examples/basic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
36
|
mesa/examples/basic/boid_flockers/Readme.md,sha256=4KJinsLPtUciQSMzvaX3tU5r1HTUg3AFOFDKy73W5RE,894
|
|
37
37
|
mesa/examples/basic/boid_flockers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
-
mesa/examples/basic/boid_flockers/agents.py,sha256=
|
|
39
|
-
mesa/examples/basic/boid_flockers/app.py,sha256=
|
|
40
|
-
mesa/examples/basic/boid_flockers/model.py,sha256=
|
|
38
|
+
mesa/examples/basic/boid_flockers/agents.py,sha256=f9IpVMpuo6WPtMffLOqgKsxoG4syJt8-k9Njvh4fMN8,3232
|
|
39
|
+
mesa/examples/basic/boid_flockers/app.py,sha256=5y52V_PRENzNGkmV_-KB5ZQeUN589i1ntJW-AIgZD0s,1323
|
|
40
|
+
mesa/examples/basic/boid_flockers/model.py,sha256=hgYScacUAv6Yq240al41r9Iv3Iqbn4wfFVQEHyorJ3c,2981
|
|
41
41
|
mesa/examples/basic/boltzmann_wealth_model/Readme.md,sha256=wl1ylO9KWoTiuIJKOnk2FGdcmyVUqJ5wiSbVUa3WWAc,2725
|
|
42
42
|
mesa/examples/basic/boltzmann_wealth_model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
43
|
mesa/examples/basic/boltzmann_wealth_model/agents.py,sha256=Jol2aspw--UtQn0EiReXVmlWtzpdC2o_YUTAyu1sDk4,1546
|
|
@@ -61,7 +61,7 @@ mesa/examples/basic/virus_on_network/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeu
|
|
|
61
61
|
mesa/examples/basic/virus_on_network/agents.py,sha256=a_WhqYblJlW6od67eXfU-nb7IMRyYpgxtf0le--VYoA,1975
|
|
62
62
|
mesa/examples/basic/virus_on_network/app.py,sha256=8I8VWQ7pBcOaNGyLDEO4IbNSTRy161-eWg-iEUVQ3-I,2553
|
|
63
63
|
mesa/examples/basic/virus_on_network/model.py,sha256=jQoCmvygwCvhUrlL0l7V8GcDLv94CgwtuK7DDGU8q8g,2813
|
|
64
|
-
mesa/experimental/__init__.py,sha256=
|
|
64
|
+
mesa/experimental/__init__.py,sha256=hSGXGvsVANvqhgA994HcLBLIRC2WhSF3e_w2gvoCMUo,943
|
|
65
65
|
mesa/experimental/cell_space/__init__.py,sha256=0A3YTen0Lk-e3Q73VEXK7N2UEHb9f50gW11H_jx7DXc,1744
|
|
66
66
|
mesa/experimental/cell_space/cell.py,sha256=Dl0ek7W_vgAZOp6zXBvkwYTG7E36OQ-4zZ5bW43mKtw,6600
|
|
67
67
|
mesa/experimental/cell_space/cell_agent.py,sha256=LOTLKR2To9hU-igKsauA5sikS7k8wgwlt-Pi0C7lGU0,4262
|
|
@@ -70,7 +70,10 @@ mesa/experimental/cell_space/discrete_space.py,sha256=qNGez9SV4E83Outs_12suuS0Zt
|
|
|
70
70
|
mesa/experimental/cell_space/grid.py,sha256=d-1S2iXijGkoJ9yc271pB8iXlzsX13usJjcjevCs_rU,10432
|
|
71
71
|
mesa/experimental/cell_space/network.py,sha256=ujN2dV1i9hcXh6H0s7gwTuPT6gh7BCaziOUYPCybQKk,1862
|
|
72
72
|
mesa/experimental/cell_space/property_layer.py,sha256=HFpBWOjI7PFU_K8VDb_pl9h62MftCBWL7PUKQNT3Ke8,17379
|
|
73
|
-
mesa/experimental/cell_space/voronoi.py,sha256=
|
|
73
|
+
mesa/experimental/cell_space/voronoi.py,sha256=FXJD8ci81Jil3FaL7ZFNfMPGvXvg3uym5Ooo1ZqKSKs,10199
|
|
74
|
+
mesa/experimental/continuous_space/__init__.py,sha256=JkCkL4zZpe8c0GHqw4huM2-uoGOYqrCyt7J1M454kFA,269
|
|
75
|
+
mesa/experimental/continuous_space/continuous_space.py,sha256=UcD-nsi5oEAJzf8ZI7BU26FYn6DdJzsW-dgXqaZIrQk,9530
|
|
76
|
+
mesa/experimental/continuous_space/continuous_space_agents.py,sha256=859ypIiWMgjknTsER0i8Y6aCWjMpw6MC5cfoKa6l_iA,3044
|
|
74
77
|
mesa/experimental/devs/__init__.py,sha256=wkDrpqQ3qHGqrsOSTD-UOj-qOw0oFgnCw_ZXr9xFs90,1200
|
|
75
78
|
mesa/experimental/devs/eventlist.py,sha256=6igPkHJt-syLcdpFV14_n6HGej2F1cM9giDQo85fHPw,7217
|
|
76
79
|
mesa/experimental/devs/simulator.py,sha256=UiVRIlNodSIveD2mS_8-vj0T_FulU8vhXxSxCfsK1Vc,12991
|
|
@@ -79,16 +82,16 @@ mesa/experimental/mesa_signals/mesa_signal.py,sha256=Vxo4gIV6a959MANL3RMANsGh0R9
|
|
|
79
82
|
mesa/experimental/mesa_signals/observable_collections.py,sha256=rHEj6BYxLHFFGzSdoDKAdtzJ6y-IHHfcP3qEDJJsY6Y,3917
|
|
80
83
|
mesa/experimental/mesa_signals/signals_util.py,sha256=fmq_FsIxsIvGjtmc4A9TGdBUtdliMHhEOpjRXivRDjA,1618
|
|
81
84
|
mesa/visualization/__init__.py,sha256=YW-oHEOTjbtDKD_TylAMtVnt8mrsz1Fw7ifdc4WeHxA,743
|
|
82
|
-
mesa/visualization/mpl_space_drawing.py,sha256=
|
|
83
|
-
mesa/visualization/solara_viz.py,sha256=
|
|
85
|
+
mesa/visualization/mpl_space_drawing.py,sha256=iqm1PYUUsmhUIraK8L9OTcTaDPDYQtlQCKtepREBA5c,20326
|
|
86
|
+
mesa/visualization/solara_viz.py,sha256=ItExWMLjg7rHb5RGlZx99YsuPhmC4i0ZCaY1MYzgqZ4,20931
|
|
84
87
|
mesa/visualization/user_param.py,sha256=Dl2WOwLYLf0pfLpabCZtIdFRyKZrK6Qtc3utZx5GPYg,2139
|
|
85
88
|
mesa/visualization/utils.py,sha256=lJHgRKF5BHLf72Tw3YpwyiWuRoIimaTKQ7xBCw_Rx3A,146
|
|
86
89
|
mesa/visualization/components/__init__.py,sha256=Bq3nrPikcaIo9BSs0O3zptWVLlUmAkLo3s0mEmpH1RE,3022
|
|
87
90
|
mesa/visualization/components/altair_components.py,sha256=wotpFFQgMY-ZR3lNVm_fRos-iDg0Wjnj6Tk67_7f1SQ,5847
|
|
88
91
|
mesa/visualization/components/matplotlib_components.py,sha256=xQETaFyHIfmL_9JwrLIgubuIQ7-pp7TMoXT1WMmozus,5441
|
|
89
|
-
mesa-3.1.
|
|
90
|
-
mesa-3.1.
|
|
91
|
-
mesa-3.1.
|
|
92
|
-
mesa-3.1.
|
|
93
|
-
mesa-3.1.
|
|
94
|
-
mesa-3.1.
|
|
92
|
+
mesa-3.1.3.dist-info/METADATA,sha256=4-LYctEQ5WYoTPl1jXxSOrIn6vfKV4lLTR_l7byd7E8,9970
|
|
93
|
+
mesa-3.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
94
|
+
mesa-3.1.3.dist-info/entry_points.txt,sha256=IOcQtetGF8l4wHpOs_hGb19Rz-FS__BMXOJR10IBPsA,39
|
|
95
|
+
mesa-3.1.3.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
96
|
+
mesa-3.1.3.dist-info/licenses/NOTICE,sha256=GbsWoK0QWv1JyZ_xer2s-jNilv0RtWl-0UrtlJANHPg,578
|
|
97
|
+
mesa-3.1.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|