Mesa 3.0.0a3__py3-none-any.whl → 3.0.0a5__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 +2 -3
- mesa/agent.py +193 -75
- mesa/batchrunner.py +18 -23
- mesa/cookiecutter-mesa/hooks/post_gen_project.py +2 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/__init__.py +1 -0
- mesa/datacollection.py +138 -27
- mesa/experimental/UserParam.py +67 -0
- mesa/experimental/__init__.py +5 -1
- mesa/experimental/cell_space/__init__.py +7 -0
- mesa/experimental/cell_space/cell.py +61 -20
- mesa/experimental/cell_space/cell_agent.py +12 -7
- mesa/experimental/cell_space/cell_collection.py +54 -17
- mesa/experimental/cell_space/discrete_space.py +16 -5
- mesa/experimental/cell_space/grid.py +19 -8
- mesa/experimental/cell_space/network.py +9 -7
- mesa/experimental/cell_space/voronoi.py +26 -33
- mesa/experimental/components/altair.py +81 -0
- mesa/experimental/components/matplotlib.py +242 -0
- mesa/experimental/devs/__init__.py +2 -0
- mesa/experimental/devs/eventlist.py +36 -15
- mesa/experimental/devs/examples/epstein_civil_violence.py +71 -39
- mesa/experimental/devs/examples/wolf_sheep.py +43 -44
- mesa/experimental/devs/simulator.py +55 -15
- mesa/experimental/solara_viz.py +453 -0
- mesa/main.py +6 -4
- mesa/model.py +64 -61
- mesa/space.py +154 -123
- mesa/time.py +57 -67
- mesa/visualization/UserParam.py +19 -6
- mesa/visualization/__init__.py +14 -2
- mesa/visualization/components/altair.py +18 -1
- mesa/visualization/components/matplotlib.py +26 -2
- mesa/visualization/solara_viz.py +231 -225
- mesa/visualization/utils.py +9 -0
- {mesa-3.0.0a3.dist-info → mesa-3.0.0a5.dist-info}/METADATA +2 -1
- mesa-3.0.0a5.dist-info/RECORD +44 -0
- mesa-3.0.0a3.dist-info/RECORD +0 -39
- {mesa-3.0.0a3.dist-info → mesa-3.0.0a5.dist-info}/WHEEL +0 -0
- {mesa-3.0.0a3.dist-info → mesa-3.0.0a5.dist-info}/entry_points.txt +0 -0
- {mesa-3.0.0a3.dist-info → mesa-3.0.0a5.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Eventlist which is at the core of event scheduling."""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
3
5
|
import itertools
|
|
@@ -10,15 +12,17 @@ from weakref import WeakMethod, ref
|
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class Priority(IntEnum):
|
|
15
|
+
"""Enumeration of priority levels."""
|
|
16
|
+
|
|
13
17
|
LOW = 10
|
|
14
18
|
DEFAULT = 5
|
|
15
19
|
HIGH = 1
|
|
16
20
|
|
|
17
21
|
|
|
18
22
|
class SimulationEvent:
|
|
19
|
-
"""A simulation event
|
|
23
|
+
"""A simulation event.
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
The callable is wrapped using weakref, so there is no need to explicitly cancel event if e.g., an agent
|
|
22
26
|
is removed from the simulation.
|
|
23
27
|
|
|
24
28
|
Attributes:
|
|
@@ -34,7 +38,7 @@ class SimulationEvent:
|
|
|
34
38
|
_ids = itertools.count()
|
|
35
39
|
|
|
36
40
|
@property
|
|
37
|
-
def CANCELED(self) -> bool:
|
|
41
|
+
def CANCELED(self) -> bool: # noqa: D102
|
|
38
42
|
return self._canceled
|
|
39
43
|
|
|
40
44
|
def __init__(
|
|
@@ -45,6 +49,15 @@ class SimulationEvent:
|
|
|
45
49
|
function_args: list[Any] | None = None,
|
|
46
50
|
function_kwargs: dict[str, Any] | None = None,
|
|
47
51
|
) -> None:
|
|
52
|
+
"""Initialize a simulation event.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
time: the instant of time of the simulation event
|
|
56
|
+
function: the callable to invoke
|
|
57
|
+
priority: the priority of the event
|
|
58
|
+
function_args: arguments for callable
|
|
59
|
+
function_kwargs: keyword arguments for the callable
|
|
60
|
+
"""
|
|
48
61
|
super().__init__()
|
|
49
62
|
if not callable(function):
|
|
50
63
|
raise Exception()
|
|
@@ -64,20 +77,20 @@ class SimulationEvent:
|
|
|
64
77
|
self.function_kwargs = function_kwargs if function_kwargs else {}
|
|
65
78
|
|
|
66
79
|
def execute(self):
|
|
67
|
-
"""
|
|
80
|
+
"""Execute this event."""
|
|
68
81
|
if not self._canceled:
|
|
69
82
|
fn = self.fn()
|
|
70
83
|
if fn is not None:
|
|
71
84
|
fn(*self.function_args, **self.function_kwargs)
|
|
72
85
|
|
|
73
86
|
def cancel(self) -> None:
|
|
74
|
-
"""
|
|
87
|
+
"""Cancel this event."""
|
|
75
88
|
self._canceled = True
|
|
76
89
|
self.fn = None
|
|
77
90
|
self.function_args = []
|
|
78
91
|
self.function_kwargs = {}
|
|
79
92
|
|
|
80
|
-
def __lt__(self, other):
|
|
93
|
+
def __lt__(self, other): # noqa
|
|
81
94
|
# Define a total ordering for events to be used by the heapq
|
|
82
95
|
return (self.time, self.priority, self.unique_id) < (
|
|
83
96
|
other.time,
|
|
@@ -87,30 +100,31 @@ class SimulationEvent:
|
|
|
87
100
|
|
|
88
101
|
|
|
89
102
|
class EventList:
|
|
90
|
-
"""An event list
|
|
103
|
+
"""An event list.
|
|
91
104
|
|
|
92
105
|
This is a heap queue sorted list of events. Events are always removed from the left, so heapq is a performant and
|
|
93
106
|
appropriate data structure. Events are sorted based on their time stamp, their priority, and their unique_id
|
|
94
107
|
as a tie-breaker, guaranteeing a complete ordering.
|
|
95
108
|
|
|
109
|
+
|
|
96
110
|
"""
|
|
97
111
|
|
|
98
112
|
def __init__(self):
|
|
113
|
+
"""Initialize an event list."""
|
|
99
114
|
self._events: list[SimulationEvent] = []
|
|
100
115
|
heapify(self._events)
|
|
101
116
|
|
|
102
117
|
def add_event(self, event: SimulationEvent):
|
|
103
|
-
"""Add the event to the event list
|
|
118
|
+
"""Add the event to the event list.
|
|
104
119
|
|
|
105
120
|
Args:
|
|
106
121
|
event (SimulationEvent): The event to be added
|
|
107
122
|
|
|
108
123
|
"""
|
|
109
|
-
|
|
110
124
|
heappush(self._events, event)
|
|
111
125
|
|
|
112
126
|
def peak_ahead(self, n: int = 1) -> list[SimulationEvent]:
|
|
113
|
-
"""Look at the first n non-canceled event in the event list
|
|
127
|
+
"""Look at the first n non-canceled event in the event list.
|
|
114
128
|
|
|
115
129
|
Args:
|
|
116
130
|
n (int): The number of events to look ahead
|
|
@@ -139,7 +153,7 @@ class EventList:
|
|
|
139
153
|
return peek
|
|
140
154
|
|
|
141
155
|
def pop_event(self) -> SimulationEvent:
|
|
142
|
-
"""
|
|
156
|
+
"""Pop the first element from the event list."""
|
|
143
157
|
while self._events:
|
|
144
158
|
event = heappop(self._events)
|
|
145
159
|
if not event.CANCELED:
|
|
@@ -147,16 +161,17 @@ class EventList:
|
|
|
147
161
|
raise IndexError("Event list is empty")
|
|
148
162
|
|
|
149
163
|
def is_empty(self) -> bool:
|
|
164
|
+
"""Return whether the event list is empty."""
|
|
150
165
|
return len(self) == 0
|
|
151
166
|
|
|
152
|
-
def __contains__(self, event: SimulationEvent) -> bool:
|
|
167
|
+
def __contains__(self, event: SimulationEvent) -> bool: # noqa
|
|
153
168
|
return event in self._events
|
|
154
169
|
|
|
155
|
-
def __len__(self) -> int:
|
|
170
|
+
def __len__(self) -> int: # noqa
|
|
156
171
|
return len(self._events)
|
|
157
172
|
|
|
158
173
|
def __repr__(self) -> str:
|
|
159
|
-
"""Return a string representation of the event list"""
|
|
174
|
+
"""Return a string representation of the event list."""
|
|
160
175
|
events_str = ", ".join(
|
|
161
176
|
[
|
|
162
177
|
f"Event(time={e.time}, priority={e.priority}, id={e.unique_id})"
|
|
@@ -167,7 +182,12 @@ class EventList:
|
|
|
167
182
|
return f"EventList([{events_str}])"
|
|
168
183
|
|
|
169
184
|
def remove(self, event: SimulationEvent) -> None:
|
|
170
|
-
"""
|
|
185
|
+
"""Remove an event from the event list.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
event (SimulationEvent): The event to be removed
|
|
189
|
+
|
|
190
|
+
"""
|
|
171
191
|
# we cannot simply remove items from _eventlist because this breaks
|
|
172
192
|
# heap structure invariant. So, we use a form of lazy deletion.
|
|
173
193
|
# SimEvents have a CANCELED flag that we set to True, while popping and peak_ahead
|
|
@@ -175,4 +195,5 @@ class EventList:
|
|
|
175
195
|
event.cancel()
|
|
176
196
|
|
|
177
197
|
def clear(self):
|
|
198
|
+
"""Clear the event list."""
|
|
178
199
|
self._events.clear()
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Epstein civil violence example using ABMSimulator."""
|
|
2
|
+
|
|
1
3
|
import enum
|
|
2
4
|
import math
|
|
3
5
|
|
|
@@ -7,21 +9,32 @@ from mesa.space import SingleGrid
|
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class EpsteinAgent(Agent):
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
"""Epstein Agent."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, model, vision, movement):
|
|
15
|
+
"""Initialize the agent.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
model: a model instance
|
|
19
|
+
vision: size of neighborhood
|
|
20
|
+
movement: boolean whether agent can move or not
|
|
21
|
+
"""
|
|
22
|
+
super().__init__(model)
|
|
12
23
|
self.vision = vision
|
|
13
24
|
self.movement = movement
|
|
14
25
|
|
|
15
26
|
|
|
16
27
|
class AgentState(enum.IntEnum):
|
|
28
|
+
"""Agent states."""
|
|
29
|
+
|
|
17
30
|
QUIESCENT = enum.auto()
|
|
18
31
|
ARRESTED = enum.auto()
|
|
19
32
|
ACTIVE = enum.auto()
|
|
20
33
|
|
|
21
34
|
|
|
22
35
|
class Citizen(EpsteinAgent):
|
|
23
|
-
"""
|
|
24
|
-
|
|
36
|
+
"""A member of the general population, may or may not be in active rebellion.
|
|
37
|
+
|
|
25
38
|
Summary of rule: If grievance - risk > threshold, rebel.
|
|
26
39
|
|
|
27
40
|
Attributes:
|
|
@@ -46,7 +59,6 @@ class Citizen(EpsteinAgent):
|
|
|
46
59
|
|
|
47
60
|
def __init__(
|
|
48
61
|
self,
|
|
49
|
-
unique_id,
|
|
50
62
|
model,
|
|
51
63
|
vision,
|
|
52
64
|
movement,
|
|
@@ -56,11 +68,13 @@ class Citizen(EpsteinAgent):
|
|
|
56
68
|
threshold,
|
|
57
69
|
arrest_prob_constant,
|
|
58
70
|
):
|
|
59
|
-
"""
|
|
60
|
-
|
|
71
|
+
"""Create a new Citizen.
|
|
72
|
+
|
|
61
73
|
Args:
|
|
62
|
-
unique_id: unique int
|
|
63
74
|
model : model instance
|
|
75
|
+
vision: number of cells in each direction (N, S, E and W) that
|
|
76
|
+
agent can inspect. Exogenous.
|
|
77
|
+
movement: whether agent can move or not
|
|
64
78
|
hardship: Agent's 'perceived hardship (i.e., physical or economic
|
|
65
79
|
privation).' Exogenous, drawn from U(0,1).
|
|
66
80
|
regime_legitimacy: Agent's perception of regime legitimacy, equal
|
|
@@ -68,10 +82,10 @@ class Citizen(EpsteinAgent):
|
|
|
68
82
|
risk_aversion: Exogenous, drawn from U(0,1).
|
|
69
83
|
threshold: if (grievance - (risk_aversion * arrest_probability)) >
|
|
70
84
|
threshold, go/remain Active
|
|
71
|
-
|
|
72
|
-
|
|
85
|
+
arrest_prob_constant : agent's assessment of arrest probability
|
|
86
|
+
|
|
73
87
|
"""
|
|
74
|
-
super().__init__(
|
|
88
|
+
super().__init__(model, vision, movement)
|
|
75
89
|
self.hardship = hardship
|
|
76
90
|
self.regime_legitimacy = regime_legitimacy
|
|
77
91
|
self.risk_aversion = risk_aversion
|
|
@@ -82,9 +96,7 @@ class Citizen(EpsteinAgent):
|
|
|
82
96
|
self.arrest_prob_constant = arrest_prob_constant
|
|
83
97
|
|
|
84
98
|
def step(self):
|
|
85
|
-
"""
|
|
86
|
-
Decide whether to activate, then move if applicable.
|
|
87
|
-
"""
|
|
99
|
+
"""Decide whether to activate, then move if applicable."""
|
|
88
100
|
self.update_neighbors()
|
|
89
101
|
self.update_estimated_arrest_probability()
|
|
90
102
|
net_risk = self.risk_aversion * self.arrest_probability
|
|
@@ -97,9 +109,7 @@ class Citizen(EpsteinAgent):
|
|
|
97
109
|
self.model.grid.move_agent(self, new_pos)
|
|
98
110
|
|
|
99
111
|
def update_neighbors(self):
|
|
100
|
-
"""
|
|
101
|
-
Look around and see who my neighbors are
|
|
102
|
-
"""
|
|
112
|
+
"""Look around and see who my neighbors are."""
|
|
103
113
|
self.neighborhood = self.model.grid.get_neighborhood(
|
|
104
114
|
self.pos, moore=True, radius=self.vision
|
|
105
115
|
)
|
|
@@ -109,10 +119,7 @@ class Citizen(EpsteinAgent):
|
|
|
109
119
|
]
|
|
110
120
|
|
|
111
121
|
def update_estimated_arrest_probability(self):
|
|
112
|
-
"""
|
|
113
|
-
Based on the ratio of cops to actives in my neighborhood, estimate the
|
|
114
|
-
p(Arrest | I go active).
|
|
115
|
-
"""
|
|
122
|
+
"""Based on the ratio of cops to actives in my neighborhood, estimate the p(Arrest | I go active)."""
|
|
116
123
|
cops_in_vision = len([c for c in self.neighbors if isinstance(c, Cop)])
|
|
117
124
|
actives_in_vision = 1.0 # citizen counts herself
|
|
118
125
|
for c in self.neighbors:
|
|
@@ -123,18 +130,25 @@ class Citizen(EpsteinAgent):
|
|
|
123
130
|
)
|
|
124
131
|
|
|
125
132
|
def sent_to_jail(self, value):
|
|
133
|
+
"""Sent agent to jail.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
value: duration of jail sentence
|
|
137
|
+
|
|
138
|
+
"""
|
|
126
139
|
self.model.active_agents.remove(self)
|
|
127
140
|
self.condition = AgentState.ARRESTED
|
|
128
141
|
self.model.simulator.schedule_event_relative(self.release_from_jail, value)
|
|
129
142
|
|
|
130
143
|
def release_from_jail(self):
|
|
144
|
+
"""Release agent from jail."""
|
|
131
145
|
self.model.active_agents.add(self)
|
|
132
146
|
self.condition = AgentState.QUIESCENT
|
|
133
147
|
|
|
134
148
|
|
|
135
149
|
class Cop(EpsteinAgent):
|
|
136
|
-
"""
|
|
137
|
-
|
|
150
|
+
"""A cop for life. No defection.
|
|
151
|
+
|
|
138
152
|
Summary of rule: Inspect local vision and arrest a random active agent.
|
|
139
153
|
|
|
140
154
|
Attributes:
|
|
@@ -144,15 +158,20 @@ class Cop(EpsteinAgent):
|
|
|
144
158
|
able to inspect
|
|
145
159
|
"""
|
|
146
160
|
|
|
147
|
-
def __init__(self,
|
|
148
|
-
|
|
161
|
+
def __init__(self, model, vision, movement, max_jail_term):
|
|
162
|
+
"""Initialize a Cop agent.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
model: a model instance
|
|
166
|
+
vision: size of neighborhood
|
|
167
|
+
movement: whether agent can move or not
|
|
168
|
+
max_jail_term: maximum jail sentence
|
|
169
|
+
"""
|
|
170
|
+
super().__init__(model, vision, movement)
|
|
149
171
|
self.max_jail_term = max_jail_term
|
|
150
172
|
|
|
151
173
|
def step(self):
|
|
152
|
-
"""
|
|
153
|
-
Inspect local vision and arrest a random active agent. Move if
|
|
154
|
-
applicable.
|
|
155
|
-
"""
|
|
174
|
+
"""Inspect local vision and arrest a random active agent. Move if applicable."""
|
|
156
175
|
self.update_neighbors()
|
|
157
176
|
active_neighbors = []
|
|
158
177
|
for agent in self.neighbors:
|
|
@@ -166,9 +185,7 @@ class Cop(EpsteinAgent):
|
|
|
166
185
|
self.model.grid.move_agent(self, new_pos)
|
|
167
186
|
|
|
168
187
|
def update_neighbors(self):
|
|
169
|
-
"""
|
|
170
|
-
Look around and see who my neighbors are.
|
|
171
|
-
"""
|
|
188
|
+
"""Look around and see who my neighbors are."""
|
|
172
189
|
self.neighborhood = self.model.grid.get_neighborhood(
|
|
173
190
|
self.pos, moore=True, radius=self.vision
|
|
174
191
|
)
|
|
@@ -179,9 +196,8 @@ class Cop(EpsteinAgent):
|
|
|
179
196
|
|
|
180
197
|
|
|
181
198
|
class EpsteinCivilViolence(Model):
|
|
182
|
-
"""
|
|
183
|
-
|
|
184
|
-
approach," by Joshua Epstein.
|
|
199
|
+
"""Model 1 from "Modeling civil violence: An agent-based computational approach," by Joshua Epstein.
|
|
200
|
+
|
|
185
201
|
http://www.pnas.org/content/99/suppl_3/7243.full
|
|
186
202
|
Attributes:
|
|
187
203
|
height: grid height
|
|
@@ -220,6 +236,23 @@ class EpsteinCivilViolence(Model):
|
|
|
220
236
|
max_iters=1000,
|
|
221
237
|
seed=None,
|
|
222
238
|
):
|
|
239
|
+
"""Initialize the Eppstein civil violence model.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
width: the width of the grid
|
|
243
|
+
height: the height of the grid
|
|
244
|
+
citizen_density: density of citizens
|
|
245
|
+
cop_density: density of cops
|
|
246
|
+
citizen_vision: size of citizen vision
|
|
247
|
+
cop_vision: size of cop vision
|
|
248
|
+
legitimacy: perceived legitimacy
|
|
249
|
+
max_jail_term: maximum jail term
|
|
250
|
+
active_threshold: threshold for citizen to become active
|
|
251
|
+
arrest_prob_constant: arrest probability
|
|
252
|
+
movement: allow agent movement or not
|
|
253
|
+
max_iters: number of iterations
|
|
254
|
+
seed: seed for random number generator
|
|
255
|
+
"""
|
|
223
256
|
super().__init__(seed)
|
|
224
257
|
if cop_density + citizen_density > 1:
|
|
225
258
|
raise ValueError("Cop density + citizen density must be less than 1")
|
|
@@ -236,7 +269,6 @@ class EpsteinCivilViolence(Model):
|
|
|
236
269
|
for _, pos in self.grid.coord_iter():
|
|
237
270
|
if self.random.random() < self.cop_density:
|
|
238
271
|
agent = Cop(
|
|
239
|
-
self.next_id(),
|
|
240
272
|
self,
|
|
241
273
|
cop_vision,
|
|
242
274
|
movement,
|
|
@@ -244,7 +276,6 @@ class EpsteinCivilViolence(Model):
|
|
|
244
276
|
)
|
|
245
277
|
elif self.random.random() < (self.cop_density + self.citizen_density):
|
|
246
278
|
agent = Citizen(
|
|
247
|
-
self.next_id(),
|
|
248
279
|
self,
|
|
249
280
|
citizen_vision,
|
|
250
281
|
movement,
|
|
@@ -261,7 +292,8 @@ class EpsteinCivilViolence(Model):
|
|
|
261
292
|
self.active_agents = self.agents
|
|
262
293
|
|
|
263
294
|
def step(self):
|
|
264
|
-
|
|
295
|
+
"""Run one step of the model."""
|
|
296
|
+
self.active_agents.shuffle_do("step")
|
|
265
297
|
|
|
266
298
|
|
|
267
299
|
if __name__ == "__main__":
|
|
@@ -270,4 +302,4 @@ if __name__ == "__main__":
|
|
|
270
302
|
|
|
271
303
|
simulator.setup(model)
|
|
272
304
|
|
|
273
|
-
simulator.
|
|
305
|
+
simulator.run_for(time_delta=100)
|
|
@@ -1,36 +1,39 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Wolf-Sheep Predation Model
|
|
3
|
-
================================
|
|
4
|
-
|
|
5
|
-
Replication of the model found in NetLogo:
|
|
6
|
-
Wilensky, U. (1997). NetLogo Wolf Sheep Predation model.
|
|
7
|
-
http://ccl.northwestern.edu/netlogo/models/WolfSheepPredation.
|
|
8
|
-
Center for Connected Learning and Computer-Based Modeling,
|
|
9
|
-
Northwestern University, Evanston, IL.
|
|
10
|
-
"""
|
|
1
|
+
"""Example of using ABM simulator for Wolf-Sheep Predation Model."""
|
|
11
2
|
|
|
12
3
|
import mesa
|
|
13
4
|
from mesa.experimental.devs.simulator import ABMSimulator
|
|
14
5
|
|
|
15
6
|
|
|
16
7
|
class Animal(mesa.Agent):
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
"""Base Animal class."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, model, moore, energy, p_reproduce, energy_from_food):
|
|
11
|
+
"""Initialize Animal instance.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
model: a model instance
|
|
15
|
+
moore: using moore grid or not
|
|
16
|
+
energy: initial energy
|
|
17
|
+
p_reproduce: probability of reproduction
|
|
18
|
+
energy_from_food: energy gained from 1 unit of food
|
|
19
|
+
"""
|
|
20
|
+
super().__init__(model)
|
|
19
21
|
self.energy = energy
|
|
20
22
|
self.p_reproduce = p_reproduce
|
|
21
23
|
self.energy_from_food = energy_from_food
|
|
22
24
|
self.moore = moore
|
|
23
25
|
|
|
24
26
|
def random_move(self):
|
|
27
|
+
"""Move to random neighboring cell."""
|
|
25
28
|
next_moves = self.model.grid.get_neighborhood(self.pos, self.moore, True)
|
|
26
29
|
next_move = self.random.choice(next_moves)
|
|
27
30
|
# Now move:
|
|
28
31
|
self.model.grid.move_agent(self, next_move)
|
|
29
32
|
|
|
30
33
|
def spawn_offspring(self):
|
|
34
|
+
"""Create offspring."""
|
|
31
35
|
self.energy /= 2
|
|
32
36
|
offspring = self.__class__(
|
|
33
|
-
self.model.next_id(),
|
|
34
37
|
self.model,
|
|
35
38
|
self.moore,
|
|
36
39
|
self.energy,
|
|
@@ -39,13 +42,15 @@ class Animal(mesa.Agent):
|
|
|
39
42
|
)
|
|
40
43
|
self.model.grid.place_agent(offspring, self.pos)
|
|
41
44
|
|
|
42
|
-
def feed(self): ...
|
|
45
|
+
def feed(self): ... # noqa: D102
|
|
43
46
|
|
|
44
47
|
def die(self):
|
|
48
|
+
"""Die."""
|
|
45
49
|
self.model.grid.remove_agent(self)
|
|
46
50
|
self.remove()
|
|
47
51
|
|
|
48
52
|
def step(self):
|
|
53
|
+
"""Execute one step of the agent."""
|
|
49
54
|
self.random_move()
|
|
50
55
|
self.energy -= 1
|
|
51
56
|
|
|
@@ -58,13 +63,10 @@ class Animal(mesa.Agent):
|
|
|
58
63
|
|
|
59
64
|
|
|
60
65
|
class Sheep(Animal):
|
|
61
|
-
"""
|
|
62
|
-
A sheep that walks around, reproduces (asexually) and gets eaten.
|
|
63
|
-
|
|
64
|
-
The init is the same as the RandomWalker.
|
|
65
|
-
"""
|
|
66
|
+
"""A sheep that walks around, reproduces (asexually) and gets eaten."""
|
|
66
67
|
|
|
67
68
|
def feed(self):
|
|
69
|
+
"""Eat grass and gain energy."""
|
|
68
70
|
# If there is grass available, eat it
|
|
69
71
|
agents = self.model.grid.get_cell_list_contents(self.pos)
|
|
70
72
|
grass_patch = next(obj for obj in agents if isinstance(obj, GrassPatch))
|
|
@@ -74,11 +76,10 @@ class Sheep(Animal):
|
|
|
74
76
|
|
|
75
77
|
|
|
76
78
|
class Wolf(Animal):
|
|
77
|
-
"""
|
|
78
|
-
A wolf that walks around, reproduces (asexually) and eats sheep.
|
|
79
|
-
"""
|
|
79
|
+
"""A wolf that walks around, reproduces (asexually) and eats sheep."""
|
|
80
80
|
|
|
81
81
|
def feed(self):
|
|
82
|
+
"""Eat wolf and gain energy."""
|
|
82
83
|
agents = self.model.grid.get_cell_list_contents(self.pos)
|
|
83
84
|
sheep = [obj for obj in agents if isinstance(obj, Sheep)]
|
|
84
85
|
if len(sheep) > 0:
|
|
@@ -90,12 +91,10 @@ class Wolf(Animal):
|
|
|
90
91
|
|
|
91
92
|
|
|
92
93
|
class GrassPatch(mesa.Agent):
|
|
93
|
-
"""
|
|
94
|
-
A patch of grass that grows at a fixed rate and it is eaten by sheep
|
|
95
|
-
"""
|
|
94
|
+
"""A patch of grass that grows at a fixed rate and it is eaten by sheep."""
|
|
96
95
|
|
|
97
96
|
@property
|
|
98
|
-
def fully_grown(self) -> bool:
|
|
97
|
+
def fully_grown(self) -> bool: # noqa: D102
|
|
99
98
|
return self._fully_grown
|
|
100
99
|
|
|
101
100
|
@fully_grown.setter
|
|
@@ -109,15 +108,16 @@ class GrassPatch(mesa.Agent):
|
|
|
109
108
|
function_args=[self, "fully_grown", True],
|
|
110
109
|
)
|
|
111
110
|
|
|
112
|
-
def __init__(self,
|
|
113
|
-
"""
|
|
114
|
-
Creates a new patch of grass
|
|
111
|
+
def __init__(self, model, fully_grown, countdown, grass_regrowth_time):
|
|
112
|
+
"""Creates a new patch of grass.
|
|
115
113
|
|
|
116
114
|
Args:
|
|
117
|
-
|
|
115
|
+
model: a model instance
|
|
116
|
+
fully_grown: (boolean) Whether the patch of grass is fully grown or not
|
|
118
117
|
countdown: Time for the patch of grass to be fully grown again
|
|
118
|
+
grass_regrowth_time: regrowth time for the grass
|
|
119
119
|
"""
|
|
120
|
-
super().__init__(
|
|
120
|
+
super().__init__(model)
|
|
121
121
|
self._fully_grown = fully_grown
|
|
122
122
|
self.grass_regrowth_time = grass_regrowth_time
|
|
123
123
|
|
|
@@ -126,13 +126,12 @@ class GrassPatch(mesa.Agent):
|
|
|
126
126
|
setattr, countdown, function_args=[self, "fully_grown", True]
|
|
127
127
|
)
|
|
128
128
|
|
|
129
|
-
def set_fully_grown(self):
|
|
129
|
+
def set_fully_grown(self): # noqa
|
|
130
130
|
self.fully_grown = True
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
class WolfSheep(mesa.Model):
|
|
134
|
-
"""
|
|
135
|
-
Wolf-Sheep Predation Model
|
|
134
|
+
"""Wolf-Sheep Predation Model.
|
|
136
135
|
|
|
137
136
|
A model for simulating wolf and sheep (predator-prey) ecosystem modelling.
|
|
138
137
|
"""
|
|
@@ -152,10 +151,11 @@ class WolfSheep(mesa.Model):
|
|
|
152
151
|
simulator=None,
|
|
153
152
|
seed=None,
|
|
154
153
|
):
|
|
155
|
-
"""
|
|
156
|
-
Create a new Wolf-Sheep model with the given parameters.
|
|
154
|
+
"""Create a new Wolf-Sheep model with the given parameters.
|
|
157
155
|
|
|
158
156
|
Args:
|
|
157
|
+
height: height of the grid
|
|
158
|
+
width: width of the grid
|
|
159
159
|
initial_sheep: Number of sheep to start with
|
|
160
160
|
initial_wolves: Number of wolves to start with
|
|
161
161
|
sheep_reproduce: Probability of each sheep reproducing each step
|
|
@@ -165,7 +165,9 @@ class WolfSheep(mesa.Model):
|
|
|
165
165
|
grass_regrowth_time: How long it takes for a grass patch to regrow
|
|
166
166
|
once it is eaten
|
|
167
167
|
sheep_gain_from_food: Energy sheep gain from grass, if enabled.
|
|
168
|
-
moore:
|
|
168
|
+
moore: whether to use moore or von Neumann grid
|
|
169
|
+
simulator: Simulator to use for simulating wolf and sheep
|
|
170
|
+
seed: Random seed
|
|
169
171
|
"""
|
|
170
172
|
super().__init__(seed=seed)
|
|
171
173
|
# Set parameters
|
|
@@ -191,7 +193,6 @@ class WolfSheep(mesa.Model):
|
|
|
191
193
|
)
|
|
192
194
|
energy = self.random.randrange(2 * sheep_gain_from_food)
|
|
193
195
|
sheep = Sheep(
|
|
194
|
-
self.next_id(),
|
|
195
196
|
self,
|
|
196
197
|
moore,
|
|
197
198
|
energy,
|
|
@@ -208,7 +209,6 @@ class WolfSheep(mesa.Model):
|
|
|
208
209
|
)
|
|
209
210
|
energy = self.random.randrange(2 * wolf_gain_from_food)
|
|
210
211
|
wolf = Wolf(
|
|
211
|
-
self.next_id(),
|
|
212
212
|
self,
|
|
213
213
|
moore,
|
|
214
214
|
energy,
|
|
@@ -225,14 +225,13 @@ class WolfSheep(mesa.Model):
|
|
|
225
225
|
countdown = grass_regrowth_time
|
|
226
226
|
else:
|
|
227
227
|
countdown = self.random.randrange(grass_regrowth_time)
|
|
228
|
-
patch = GrassPatch(
|
|
229
|
-
self.next_id(), self, fully_grown, countdown, grass_regrowth_time
|
|
230
|
-
)
|
|
228
|
+
patch = GrassPatch(self, fully_grown, countdown, grass_regrowth_time)
|
|
231
229
|
self.grid.place_agent(patch, pos)
|
|
232
230
|
|
|
233
231
|
def step(self):
|
|
234
|
-
|
|
235
|
-
self.
|
|
232
|
+
"""Perform one step of the model."""
|
|
233
|
+
self.agents_by_type[Sheep].shuffle_do("step")
|
|
234
|
+
self.agents_by_type[Wolf].shuffle_do("step")
|
|
236
235
|
|
|
237
236
|
|
|
238
237
|
if __name__ == "__main__":
|