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
|
@@ -5,14 +5,14 @@ class Animal(CellAgent):
|
|
|
5
5
|
"""The base animal class."""
|
|
6
6
|
|
|
7
7
|
def __init__(self, model, energy, p_reproduce, energy_from_food, cell):
|
|
8
|
-
"""
|
|
8
|
+
"""Initialize an animal.
|
|
9
9
|
|
|
10
10
|
Args:
|
|
11
|
-
model:
|
|
12
|
-
energy:
|
|
13
|
-
p_reproduce:
|
|
14
|
-
energy_from_food:
|
|
15
|
-
cell:
|
|
11
|
+
model: Model instance
|
|
12
|
+
energy: Starting amount of energy
|
|
13
|
+
p_reproduce: Probability of reproduction (asexual)
|
|
14
|
+
energy_from_food: Energy obtained from 1 unit of food
|
|
15
|
+
cell: Cell in which the animal starts
|
|
16
16
|
"""
|
|
17
17
|
super().__init__(model)
|
|
18
18
|
self.energy = energy
|
|
@@ -21,7 +21,7 @@ class Animal(CellAgent):
|
|
|
21
21
|
self.cell = cell
|
|
22
22
|
|
|
23
23
|
def spawn_offspring(self):
|
|
24
|
-
"""Create offspring."""
|
|
24
|
+
"""Create offspring by splitting energy and creating new instance."""
|
|
25
25
|
self.energy /= 2
|
|
26
26
|
self.__class__(
|
|
27
27
|
self.model,
|
|
@@ -31,15 +31,19 @@ class Animal(CellAgent):
|
|
|
31
31
|
self.cell,
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
-
def feed(self):
|
|
34
|
+
def feed(self):
|
|
35
|
+
"""Abstract method to be implemented by subclasses."""
|
|
35
36
|
|
|
36
37
|
def step(self):
|
|
37
|
-
"""
|
|
38
|
+
"""Execute one step of the animal's behavior."""
|
|
39
|
+
# Move to random neighboring cell
|
|
38
40
|
self.cell = self.cell.neighborhood.select_random_cell()
|
|
39
41
|
self.energy -= 1
|
|
40
42
|
|
|
43
|
+
# Try to feed
|
|
41
44
|
self.feed()
|
|
42
45
|
|
|
46
|
+
# Handle death and reproduction
|
|
43
47
|
if self.energy < 0:
|
|
44
48
|
self.remove()
|
|
45
49
|
elif self.random.random() < self.p_reproduce:
|
|
@@ -50,53 +54,63 @@ class Sheep(Animal):
|
|
|
50
54
|
"""A sheep that walks around, reproduces (asexually) and gets eaten."""
|
|
51
55
|
|
|
52
56
|
def feed(self):
|
|
53
|
-
"""If possible eat
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
self.energy += self.energy_from_food
|
|
61
|
-
grass_patch.fully_grown = False
|
|
57
|
+
"""If possible, eat grass at current location."""
|
|
58
|
+
grass_patch = next(
|
|
59
|
+
obj for obj in self.cell.agents if isinstance(obj, GrassPatch)
|
|
60
|
+
)
|
|
61
|
+
if grass_patch.fully_grown:
|
|
62
|
+
self.energy += self.energy_from_food
|
|
63
|
+
grass_patch.fully_grown = False
|
|
62
64
|
|
|
63
65
|
|
|
64
66
|
class Wolf(Animal):
|
|
65
67
|
"""A wolf that walks around, reproduces (asexually) and eats sheep."""
|
|
66
68
|
|
|
67
69
|
def feed(self):
|
|
68
|
-
"""If possible eat
|
|
70
|
+
"""If possible, eat a sheep at current location."""
|
|
69
71
|
sheep = [obj for obj in self.cell.agents if isinstance(obj, Sheep)]
|
|
70
|
-
if
|
|
72
|
+
if sheep: # If there are any sheep present
|
|
71
73
|
sheep_to_eat = self.random.choice(sheep)
|
|
72
74
|
self.energy += self.energy_from_food
|
|
73
|
-
|
|
74
|
-
# Kill the sheep
|
|
75
75
|
sheep_to_eat.remove()
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
class GrassPatch(FixedAgent):
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
"""A patch of grass that grows at a fixed rate and can be eaten by sheep."""
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def fully_grown(self):
|
|
83
|
+
"""Whether the grass patch is fully grown."""
|
|
84
|
+
return self._fully_grown
|
|
85
|
+
|
|
86
|
+
@fully_grown.setter
|
|
87
|
+
def fully_grown(self, value: bool) -> None:
|
|
88
|
+
"""Set grass growth state and schedule regrowth if eaten."""
|
|
89
|
+
self._fully_grown = value
|
|
90
|
+
|
|
91
|
+
if not value: # If grass was just eaten
|
|
92
|
+
self.model.simulator.schedule_event_relative(
|
|
93
|
+
setattr,
|
|
94
|
+
self.grass_regrowth_time,
|
|
95
|
+
function_args=[self, "fully_grown", True],
|
|
96
|
+
)
|
|
82
97
|
|
|
83
|
-
def __init__(self, model,
|
|
84
|
-
"""
|
|
85
|
-
Creates a new patch of grass
|
|
98
|
+
def __init__(self, model, countdown, grass_regrowth_time, cell):
|
|
99
|
+
"""Create a new patch of grass.
|
|
86
100
|
|
|
87
101
|
Args:
|
|
88
|
-
|
|
89
|
-
countdown: Time
|
|
102
|
+
model: Model instance
|
|
103
|
+
countdown: Time until grass is fully grown again
|
|
104
|
+
grass_regrowth_time: Time needed to regrow after being eaten
|
|
105
|
+
cell: Cell to which this grass patch belongs
|
|
90
106
|
"""
|
|
91
107
|
super().__init__(model)
|
|
92
|
-
self.
|
|
93
|
-
self.
|
|
108
|
+
self._fully_grown = countdown == 0
|
|
109
|
+
self.grass_regrowth_time = grass_regrowth_time
|
|
110
|
+
self.cell = cell
|
|
94
111
|
|
|
95
|
-
|
|
112
|
+
# Schedule initial growth if not fully grown
|
|
96
113
|
if not self.fully_grown:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
self.countdown = self.model.grass_regrowth_time
|
|
101
|
-
else:
|
|
102
|
-
self.countdown -= 1
|
|
114
|
+
self.model.simulator.schedule_event_relative(
|
|
115
|
+
setattr, countdown, function_args=[self, "fully_grown", True]
|
|
116
|
+
)
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf
|
|
2
2
|
from mesa.examples.advanced.wolf_sheep.model import WolfSheep
|
|
3
|
+
from mesa.experimental.devs import ABMSimulator
|
|
3
4
|
from mesa.visualization import (
|
|
4
5
|
Slider,
|
|
5
6
|
SolaraViz,
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
make_plot_component,
|
|
8
|
+
make_space_component,
|
|
8
9
|
)
|
|
9
10
|
|
|
10
|
-
WOLF_COLOR = "#000000"
|
|
11
|
-
SHEEP_COLOR = "#648FFF"
|
|
12
|
-
|
|
13
11
|
|
|
14
12
|
def wolf_sheep_portrayal(agent):
|
|
15
13
|
if agent is None:
|
|
@@ -17,29 +15,33 @@ def wolf_sheep_portrayal(agent):
|
|
|
17
15
|
|
|
18
16
|
portrayal = {
|
|
19
17
|
"size": 25,
|
|
20
|
-
"shape": "s", # square marker
|
|
21
18
|
}
|
|
22
19
|
|
|
23
20
|
if isinstance(agent, Wolf):
|
|
24
|
-
portrayal["color"] =
|
|
25
|
-
portrayal["
|
|
21
|
+
portrayal["color"] = "tab:red"
|
|
22
|
+
portrayal["marker"] = "o"
|
|
23
|
+
portrayal["zorder"] = 2
|
|
26
24
|
elif isinstance(agent, Sheep):
|
|
27
|
-
portrayal["color"] =
|
|
28
|
-
portrayal["
|
|
25
|
+
portrayal["color"] = "tab:cyan"
|
|
26
|
+
portrayal["marker"] = "o"
|
|
27
|
+
portrayal["zorder"] = 2
|
|
29
28
|
elif isinstance(agent, GrassPatch):
|
|
30
29
|
if agent.fully_grown:
|
|
31
|
-
portrayal["color"] = "
|
|
30
|
+
portrayal["color"] = "tab:green"
|
|
32
31
|
else:
|
|
33
|
-
portrayal["color"] = "
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
portrayal["Layer"] = 1
|
|
32
|
+
portrayal["color"] = "tab:brown"
|
|
33
|
+
portrayal["marker"] = "s"
|
|
34
|
+
portrayal["size"] = 75
|
|
37
35
|
|
|
38
36
|
return portrayal
|
|
39
37
|
|
|
40
38
|
|
|
41
39
|
model_params = {
|
|
42
|
-
|
|
40
|
+
"seed": {
|
|
41
|
+
"type": "InputText",
|
|
42
|
+
"value": 42,
|
|
43
|
+
"label": "Random Seed",
|
|
44
|
+
},
|
|
43
45
|
"grass": {
|
|
44
46
|
"type": "Select",
|
|
45
47
|
"value": True,
|
|
@@ -62,16 +64,32 @@ model_params = {
|
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
def post_process_space(ax):
|
|
68
|
+
ax.set_aspect("equal")
|
|
69
|
+
ax.set_xticks([])
|
|
70
|
+
ax.set_yticks([])
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def post_process_lines(ax):
|
|
74
|
+
ax.legend(loc="center left", bbox_to_anchor=(1, 0.9))
|
|
67
75
|
|
|
68
|
-
model = WolfSheep()
|
|
69
76
|
|
|
77
|
+
space_component = make_space_component(
|
|
78
|
+
wolf_sheep_portrayal, draw_grid=False, post_process=post_process_space
|
|
79
|
+
)
|
|
80
|
+
lineplot_component = make_plot_component(
|
|
81
|
+
{"Wolves": "tab:orange", "Sheep": "tab:cyan", "Grass": "tab:green"},
|
|
82
|
+
post_process=post_process_lines,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
simulator = ABMSimulator()
|
|
86
|
+
model = WolfSheep(simulator, grass=True)
|
|
70
87
|
|
|
71
88
|
page = SolaraViz(
|
|
72
89
|
model,
|
|
73
90
|
components=[space_component, lineplot_component],
|
|
74
91
|
model_params=model_params,
|
|
75
92
|
name="Wolf Sheep",
|
|
93
|
+
simulator=simulator,
|
|
76
94
|
)
|
|
77
95
|
page # noqa
|
|
@@ -9,30 +9,20 @@ Replication of the model found in NetLogo:
|
|
|
9
9
|
Northwestern University, Evanston, IL.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
import
|
|
13
|
-
from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf
|
|
14
|
-
from mesa.experimental.cell_space import OrthogonalMooreGrid
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class WolfSheep(mesa.Model):
|
|
18
|
-
"""
|
|
19
|
-
Wolf-Sheep Predation Model
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
height = 20
|
|
23
|
-
width = 20
|
|
12
|
+
import math
|
|
24
13
|
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
from mesa import Model
|
|
15
|
+
from mesa.datacollection import DataCollector
|
|
16
|
+
from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf
|
|
17
|
+
from mesa.experimental.cell_space import OrthogonalVonNeumannGrid
|
|
18
|
+
from mesa.experimental.devs import ABMSimulator
|
|
27
19
|
|
|
28
|
-
sheep_reproduce = 0.04
|
|
29
|
-
wolf_reproduce = 0.05
|
|
30
20
|
|
|
31
|
-
|
|
21
|
+
class WolfSheep(Model):
|
|
22
|
+
"""Wolf-Sheep Predation Model.
|
|
32
23
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
sheep_gain_from_food = 4
|
|
24
|
+
A model for simulating wolf and sheep (predator-prey) ecosystem modelling.
|
|
25
|
+
"""
|
|
36
26
|
|
|
37
27
|
description = (
|
|
38
28
|
"A model for simulating wolf and sheep (predator-prey) ecosystem modelling."
|
|
@@ -47,15 +37,17 @@ class WolfSheep(mesa.Model):
|
|
|
47
37
|
sheep_reproduce=0.04,
|
|
48
38
|
wolf_reproduce=0.05,
|
|
49
39
|
wolf_gain_from_food=20,
|
|
50
|
-
grass=
|
|
40
|
+
grass=True,
|
|
51
41
|
grass_regrowth_time=30,
|
|
52
42
|
sheep_gain_from_food=4,
|
|
53
43
|
seed=None,
|
|
44
|
+
simulator: ABMSimulator = None,
|
|
54
45
|
):
|
|
55
|
-
"""
|
|
56
|
-
Create a new Wolf-Sheep model with the given parameters.
|
|
46
|
+
"""Create a new Wolf-Sheep model with the given parameters.
|
|
57
47
|
|
|
58
48
|
Args:
|
|
49
|
+
height: Height of the grid
|
|
50
|
+
width: Width of the grid
|
|
59
51
|
initial_sheep: Number of sheep to start with
|
|
60
52
|
initial_wolves: Number of wolves to start with
|
|
61
53
|
sheep_reproduce: Probability of each sheep reproducing each step
|
|
@@ -63,75 +55,77 @@ class WolfSheep(mesa.Model):
|
|
|
63
55
|
wolf_gain_from_food: Energy a wolf gains from eating a sheep
|
|
64
56
|
grass: Whether to have the sheep eat grass for energy
|
|
65
57
|
grass_regrowth_time: How long it takes for a grass patch to regrow
|
|
66
|
-
|
|
67
|
-
sheep_gain_from_food: Energy sheep gain from grass, if enabled
|
|
58
|
+
once it is eaten
|
|
59
|
+
sheep_gain_from_food: Energy sheep gain from grass, if enabled
|
|
60
|
+
seed: Random seed
|
|
61
|
+
simulator: ABMSimulator instance for event scheduling
|
|
68
62
|
"""
|
|
69
63
|
super().__init__(seed=seed)
|
|
70
|
-
|
|
71
|
-
self.
|
|
64
|
+
self.simulator = simulator
|
|
65
|
+
self.simulator.setup(self)
|
|
66
|
+
|
|
67
|
+
# Initialize model parameters
|
|
72
68
|
self.height = height
|
|
73
|
-
self.
|
|
74
|
-
self.initial_wolves = initial_wolves
|
|
69
|
+
self.width = width
|
|
75
70
|
self.grass = grass
|
|
76
|
-
self.grass_regrowth_time = grass_regrowth_time
|
|
77
71
|
|
|
78
|
-
|
|
72
|
+
# Create grid using experimental cell space
|
|
73
|
+
self.grid = OrthogonalVonNeumannGrid(
|
|
74
|
+
[self.height, self.width],
|
|
75
|
+
torus=True,
|
|
76
|
+
capacity=math.inf,
|
|
77
|
+
random=self.random,
|
|
78
|
+
)
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
# Set up data collection
|
|
81
|
+
model_reporters = {
|
|
81
82
|
"Wolves": lambda m: len(m.agents_by_type[Wolf]),
|
|
82
83
|
"Sheep": lambda m: len(m.agents_by_type[Sheep]),
|
|
83
|
-
|
|
84
|
+
}
|
|
85
|
+
if grass:
|
|
86
|
+
model_reporters["Grass"] = lambda m: len(
|
|
84
87
|
m.agents_by_type[GrassPatch].select(lambda a: a.fully_grown)
|
|
85
88
|
)
|
|
86
|
-
if m.grass
|
|
87
|
-
else -1,
|
|
88
|
-
}
|
|
89
89
|
|
|
90
|
-
self.datacollector =
|
|
90
|
+
self.datacollector = DataCollector(model_reporters)
|
|
91
91
|
|
|
92
92
|
# Create sheep:
|
|
93
|
-
for _ in range(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
Sheep(
|
|
98
|
-
self, energy, sheep_reproduce, sheep_gain_from_food, self.grid[(x, y)]
|
|
93
|
+
for _ in range(initial_sheep):
|
|
94
|
+
pos = (
|
|
95
|
+
self.random.randrange(width),
|
|
96
|
+
self.random.randrange(height),
|
|
99
97
|
)
|
|
98
|
+
energy = self.random.randrange(2 * sheep_gain_from_food)
|
|
99
|
+
Sheep(self, energy, sheep_reproduce, sheep_gain_from_food, self.grid[pos])
|
|
100
100
|
|
|
101
101
|
# Create wolves
|
|
102
|
-
for _ in range(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
102
|
+
for _ in range(initial_wolves):
|
|
103
|
+
pos = (
|
|
104
|
+
self.random.randrange(width),
|
|
105
|
+
self.random.randrange(height),
|
|
106
|
+
)
|
|
107
|
+
energy = self.random.randrange(2 * wolf_gain_from_food)
|
|
108
|
+
Wolf(self, energy, wolf_reproduce, wolf_gain_from_food, self.grid[pos])
|
|
109
|
+
|
|
110
|
+
# Create grass patches if enabled
|
|
111
|
+
if grass:
|
|
112
|
+
possibly_fully_grown = [True, False]
|
|
113
|
+
for cell in self.grid:
|
|
114
|
+
fully_grown = self.random.choice(possibly_fully_grown)
|
|
115
|
+
countdown = (
|
|
116
|
+
0 if fully_grown else self.random.randrange(0, grass_regrowth_time)
|
|
117
|
+
)
|
|
118
|
+
GrassPatch(self, countdown, grass_regrowth_time, cell)
|
|
119
|
+
|
|
120
|
+
# Collect initial data
|
|
121
121
|
self.running = True
|
|
122
122
|
self.datacollector.collect(self)
|
|
123
123
|
|
|
124
124
|
def step(self):
|
|
125
|
-
|
|
126
|
-
#
|
|
127
|
-
|
|
128
|
-
self.
|
|
129
|
-
for agent_type in self.agent_types:
|
|
130
|
-
self.agents_by_type[agent_type].shuffle_do("step")
|
|
131
|
-
|
|
132
|
-
# collect data
|
|
133
|
-
self.datacollector.collect(self)
|
|
125
|
+
"""Execute one step of the model."""
|
|
126
|
+
# First activate all sheep, then all wolves, both in random order
|
|
127
|
+
self.agents_by_type[Sheep].shuffle_do("step")
|
|
128
|
+
self.agents_by_type[Wolf].shuffle_do("step")
|
|
134
129
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
self.step()
|
|
130
|
+
# Collect data
|
|
131
|
+
self.datacollector.collect(self)
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
"""A Boid (bird-oid) agent for implementing Craig Reynolds's Boids flocking model.
|
|
2
|
+
|
|
3
|
+
This implementation uses numpy arrays to represent vectors for efficient computation
|
|
4
|
+
of flocking behavior.
|
|
5
|
+
"""
|
|
6
|
+
|
|
1
7
|
import numpy as np
|
|
2
8
|
|
|
3
9
|
from mesa import Agent
|
|
@@ -7,9 +13,9 @@ class Boid(Agent):
|
|
|
7
13
|
"""A Boid-style flocker agent.
|
|
8
14
|
|
|
9
15
|
The agent follows three behaviors to flock:
|
|
10
|
-
- Cohesion: steering towards neighboring agents
|
|
11
|
-
- Separation: avoiding getting too close to any other agent
|
|
12
|
-
- Alignment:
|
|
16
|
+
- Cohesion: steering towards neighboring agents
|
|
17
|
+
- Separation: avoiding getting too close to any other agent
|
|
18
|
+
- Alignment: trying to fly in the same direction as neighbors
|
|
13
19
|
|
|
14
20
|
Boids have a vision that defines the radius in which they look for their
|
|
15
21
|
neighbors to flock with. Their speed (a scalar) and direction (a vector)
|
|
@@ -31,13 +37,14 @@ class Boid(Agent):
|
|
|
31
37
|
"""Create a new Boid flocker agent.
|
|
32
38
|
|
|
33
39
|
Args:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
model: Model instance the agent belongs to
|
|
41
|
+
speed: Distance to move per step
|
|
42
|
+
direction: numpy vector for the Boid's direction of movement
|
|
43
|
+
vision: Radius to look around for nearby Boids
|
|
44
|
+
separation: Minimum distance to maintain from other Boids
|
|
45
|
+
cohere: Relative importance of matching neighbors' positions (default: 0.03)
|
|
46
|
+
separate: Relative importance of avoiding close neighbors (default: 0.015)
|
|
47
|
+
match: Relative importance of matching neighbors' directions (default: 0.05)
|
|
41
48
|
"""
|
|
42
49
|
super().__init__(model)
|
|
43
50
|
self.speed = speed
|
|
@@ -47,25 +54,49 @@ class Boid(Agent):
|
|
|
47
54
|
self.cohere_factor = cohere
|
|
48
55
|
self.separate_factor = separate
|
|
49
56
|
self.match_factor = match
|
|
50
|
-
self.neighbors = None
|
|
51
57
|
|
|
52
58
|
def step(self):
|
|
53
59
|
"""Get the Boid's neighbors, compute the new vector, and move accordingly."""
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
neighbors = self.model.space.get_neighbors(self.pos, self.vision, False)
|
|
61
|
+
|
|
62
|
+
# If no neighbors, maintain current direction
|
|
63
|
+
if not neighbors:
|
|
64
|
+
new_pos = self.pos + self.direction * self.speed
|
|
65
|
+
self.model.space.move_agent(self, new_pos)
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
# Initialize vectors for the three flocking behaviors
|
|
69
|
+
cohere = np.zeros(2) # Cohesion vector
|
|
70
|
+
match_vector = np.zeros(2) # Alignment vector
|
|
71
|
+
separation_vector = np.zeros(2) # Separation vector
|
|
72
|
+
|
|
73
|
+
# Calculate the contribution of each neighbor to the three behaviors
|
|
74
|
+
for neighbor in neighbors:
|
|
59
75
|
heading = self.model.space.get_heading(self.pos, neighbor.pos)
|
|
76
|
+
distance = self.model.space.get_distance(self.pos, neighbor.pos)
|
|
77
|
+
|
|
78
|
+
# Cohesion - steer towards the average position of neighbors
|
|
60
79
|
cohere += heading
|
|
61
|
-
|
|
80
|
+
|
|
81
|
+
# Separation - avoid getting too close
|
|
82
|
+
if distance < self.separation:
|
|
62
83
|
separation_vector -= heading
|
|
84
|
+
|
|
85
|
+
# Alignment - match neighbors' flying direction
|
|
63
86
|
match_vector += neighbor.direction
|
|
64
|
-
|
|
87
|
+
|
|
88
|
+
# Weight each behavior by its factor and normalize by number of neighbors
|
|
89
|
+
n = len(neighbors)
|
|
65
90
|
cohere = cohere * self.cohere_factor
|
|
66
91
|
separation_vector = separation_vector * self.separate_factor
|
|
67
92
|
match_vector = match_vector * self.match_factor
|
|
93
|
+
|
|
94
|
+
# Update direction based on the three behaviors
|
|
68
95
|
self.direction += (cohere + separation_vector + match_vector) / n
|
|
96
|
+
|
|
97
|
+
# Normalize direction vector
|
|
69
98
|
self.direction /= np.linalg.norm(self.direction)
|
|
99
|
+
|
|
100
|
+
# Move boid
|
|
70
101
|
new_pos = self.pos + self.direction * self.speed
|
|
71
102
|
self.model.space.move_agent(self, new_pos)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from mesa.examples.basic.boid_flockers.model import BoidFlockers
|
|
2
|
-
from mesa.visualization import Slider, SolaraViz,
|
|
2
|
+
from mesa.visualization import Slider, SolaraViz, make_space_component
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def boid_draw(agent):
|
|
@@ -51,7 +51,7 @@ model = BoidFlockers()
|
|
|
51
51
|
|
|
52
52
|
page = SolaraViz(
|
|
53
53
|
model,
|
|
54
|
-
[
|
|
54
|
+
[make_space_component(agent_portrayal=boid_draw, backend="matplotlib")],
|
|
55
55
|
model_params=model_params,
|
|
56
56
|
name="Boid Flocking Model",
|
|
57
57
|
)
|