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
mesa/__init__.py
CHANGED
|
@@ -24,7 +24,7 @@ __all__ = [
|
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
__title__ = "mesa"
|
|
27
|
-
__version__ = "3.0.
|
|
27
|
+
__version__ = "3.0.1"
|
|
28
28
|
__license__ = "Apache 2.0"
|
|
29
29
|
_this_year = datetime.datetime.now(tz=datetime.timezone.utc).date().year
|
|
30
30
|
__copyright__ = f"Copyright {_this_year} Project Mesa Team"
|
mesa/agent.py
CHANGED
|
@@ -110,6 +110,12 @@ class AgentSet(MutableSet, Sequence):
|
|
|
110
110
|
without preventing garbage collection. It is associated with a specific model instance, enabling
|
|
111
111
|
interactions with the model's environment and other agents.The implementation uses a WeakKeyDictionary to store agents,
|
|
112
112
|
which means that agents not referenced elsewhere in the program may be automatically removed from the AgentSet.
|
|
113
|
+
|
|
114
|
+
Notes:
|
|
115
|
+
A `UserWarning` is issued if `random=None`. You can resolve this warning by explicitly
|
|
116
|
+
passing a random number generator. In most cases, this will be the seeded random number
|
|
117
|
+
generator in the model. So, you would do `random=self.random` in a `Model` or `Agent` instance.
|
|
118
|
+
|
|
113
119
|
"""
|
|
114
120
|
|
|
115
121
|
def __init__(self, agents: Iterable[Agent], random: Random | None = None):
|
|
@@ -120,6 +126,11 @@ class AgentSet(MutableSet, Sequence):
|
|
|
120
126
|
random (Random): the random number generator
|
|
121
127
|
"""
|
|
122
128
|
if random is None:
|
|
129
|
+
warnings.warn(
|
|
130
|
+
"Random number generator not specified, this can make models non-reproducible. Please pass a random number generator explicitly",
|
|
131
|
+
UserWarning,
|
|
132
|
+
stacklevel=2,
|
|
133
|
+
)
|
|
123
134
|
random = (
|
|
124
135
|
Random()
|
|
125
136
|
) # FIXME see issue 1981, how to get the central rng from model
|
|
@@ -167,7 +178,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
167
178
|
"""
|
|
168
179
|
if n is not None:
|
|
169
180
|
warnings.warn(
|
|
170
|
-
"The parameter 'n' is deprecated. Use 'at_most' instead.",
|
|
181
|
+
"The parameter 'n' is deprecated and will be removed in Mesa 3.1. Use 'at_most' instead.",
|
|
171
182
|
DeprecationWarning,
|
|
172
183
|
stacklevel=2,
|
|
173
184
|
)
|
|
@@ -276,8 +287,9 @@ class AgentSet(MutableSet, Sequence):
|
|
|
276
287
|
return_results = False
|
|
277
288
|
else:
|
|
278
289
|
warnings.warn(
|
|
279
|
-
"Using return_results is deprecated
|
|
280
|
-
"AgentSet.map in case of return_results=True",
|
|
290
|
+
"Using return_results is deprecated and will be removed in Mesa 3.1."
|
|
291
|
+
"Use AgenSet.do in case of return_results=False, and AgentSet.map in case of return_results=True",
|
|
292
|
+
DeprecationWarning,
|
|
281
293
|
stacklevel=2,
|
|
282
294
|
)
|
|
283
295
|
|
mesa/examples/__init__.py
CHANGED
|
@@ -3,14 +3,14 @@ from mesa.examples.advanced.pd_grid.model import PdGrid
|
|
|
3
3
|
from mesa.examples.advanced.sugarscape_g1mt.model import SugarscapeG1mt
|
|
4
4
|
from mesa.examples.advanced.wolf_sheep.model import WolfSheep
|
|
5
5
|
from mesa.examples.basic.boid_flockers.model import BoidFlockers
|
|
6
|
-
from mesa.examples.basic.boltzmann_wealth_model.model import
|
|
6
|
+
from mesa.examples.basic.boltzmann_wealth_model.model import BoltzmannWealth
|
|
7
7
|
from mesa.examples.basic.conways_game_of_life.model import ConwaysGameOfLife
|
|
8
8
|
from mesa.examples.basic.schelling.model import Schelling
|
|
9
9
|
from mesa.examples.basic.virus_on_network.model import VirusOnNetwork
|
|
10
10
|
|
|
11
11
|
__all__ = [
|
|
12
12
|
"BoidFlockers",
|
|
13
|
-
"
|
|
13
|
+
"BoltzmannWealth",
|
|
14
14
|
"ConwaysGameOfLife",
|
|
15
15
|
"Schelling",
|
|
16
16
|
"VirusOnNetwork",
|
|
@@ -4,11 +4,11 @@ Solara-based visualization for the Spatial Prisoner's Dilemma Model.
|
|
|
4
4
|
|
|
5
5
|
from mesa.examples.advanced.pd_grid.model import PdGrid
|
|
6
6
|
from mesa.visualization import (
|
|
7
|
+
Slider,
|
|
7
8
|
SolaraViz,
|
|
8
9
|
make_plot_component,
|
|
9
10
|
make_space_component,
|
|
10
11
|
)
|
|
11
|
-
from mesa.visualization.UserParam import Slider
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def pd_agent_portrayal(agent):
|
|
@@ -27,7 +27,7 @@ class PdGrid(mesa.Model):
|
|
|
27
27
|
"""
|
|
28
28
|
super().__init__(seed=seed)
|
|
29
29
|
self.activation_order = activation_order
|
|
30
|
-
self.grid = OrthogonalMooreGrid((width, height), torus=True)
|
|
30
|
+
self.grid = OrthogonalMooreGrid((width, height), torus=True, random=self.random)
|
|
31
31
|
|
|
32
32
|
if payoffs is not None:
|
|
33
33
|
self.payoff = payoffs
|
|
@@ -70,7 +70,9 @@ class SugarscapeG1mt(mesa.Model):
|
|
|
70
70
|
self.running = True
|
|
71
71
|
|
|
72
72
|
# initiate mesa grid class
|
|
73
|
-
self.grid = OrthogonalVonNeumannGrid(
|
|
73
|
+
self.grid = OrthogonalVonNeumannGrid(
|
|
74
|
+
(self.width, self.height), torus=False, random=self.random
|
|
75
|
+
)
|
|
74
76
|
# initiate datacollector
|
|
75
77
|
self.datacollector = mesa.DataCollector(
|
|
76
78
|
model_reporters={
|
|
@@ -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,5 +1,6 @@
|
|
|
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,
|
|
@@ -36,7 +37,11 @@ def wolf_sheep_portrayal(agent):
|
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
model_params = {
|
|
39
|
-
|
|
40
|
+
"seed": {
|
|
41
|
+
"type": "InputText",
|
|
42
|
+
"value": 42,
|
|
43
|
+
"label": "Random Seed",
|
|
44
|
+
},
|
|
40
45
|
"grass": {
|
|
41
46
|
"type": "Select",
|
|
42
47
|
"value": True,
|
|
@@ -59,26 +64,32 @@ model_params = {
|
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
|
|
62
|
-
def
|
|
67
|
+
def post_process_space(ax):
|
|
63
68
|
ax.set_aspect("equal")
|
|
64
69
|
ax.set_xticks([])
|
|
65
70
|
ax.set_yticks([])
|
|
66
71
|
|
|
67
72
|
|
|
73
|
+
def post_process_lines(ax):
|
|
74
|
+
ax.legend(loc="center left", bbox_to_anchor=(1, 0.9))
|
|
75
|
+
|
|
76
|
+
|
|
68
77
|
space_component = make_space_component(
|
|
69
|
-
wolf_sheep_portrayal, draw_grid=False, post_process=
|
|
78
|
+
wolf_sheep_portrayal, draw_grid=False, post_process=post_process_space
|
|
70
79
|
)
|
|
71
80
|
lineplot_component = make_plot_component(
|
|
72
|
-
{"Wolves": "tab:orange", "Sheep": "tab:cyan", "Grass": "tab:green"}
|
|
81
|
+
{"Wolves": "tab:orange", "Sheep": "tab:cyan", "Grass": "tab:green"},
|
|
82
|
+
post_process=post_process_lines,
|
|
73
83
|
)
|
|
74
84
|
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
simulator = ABMSimulator()
|
|
86
|
+
model = WolfSheep(simulator, grass=True)
|
|
77
87
|
|
|
78
88
|
page = SolaraViz(
|
|
79
89
|
model,
|
|
80
90
|
components=[space_component, lineplot_component],
|
|
81
91
|
model_params=model_params,
|
|
82
92
|
name="Wolf Sheep",
|
|
93
|
+
simulator=simulator,
|
|
83
94
|
)
|
|
84
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)
|