Mesa 3.0.0a4__py3-none-any.whl → 3.0.0b0__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 +116 -85
- mesa/batchrunner.py +22 -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 +17 -6
- mesa/experimental/__init__.py +2 -0
- mesa/experimental/cell_space/__init__.py +14 -1
- mesa/experimental/cell_space/cell.py +84 -23
- mesa/experimental/cell_space/cell_agent.py +117 -21
- mesa/experimental/cell_space/cell_collection.py +54 -17
- mesa/experimental/cell_space/discrete_space.py +79 -7
- 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 +10 -0
- mesa/experimental/components/matplotlib.py +18 -0
- mesa/experimental/devs/__init__.py +2 -0
- mesa/experimental/devs/eventlist.py +36 -15
- mesa/experimental/devs/examples/epstein_civil_violence.py +65 -29
- mesa/experimental/devs/examples/wolf_sheep.py +40 -35
- mesa/experimental/devs/simulator.py +55 -15
- mesa/experimental/solara_viz.py +10 -19
- mesa/main.py +6 -4
- mesa/model.py +51 -54
- mesa/space.py +145 -120
- mesa/time.py +57 -67
- mesa/visualization/UserParam.py +19 -6
- mesa/visualization/__init__.py +3 -2
- mesa/visualization/components/altair.py +4 -2
- mesa/visualization/components/matplotlib.py +176 -85
- mesa/visualization/solara_viz.py +167 -84
- mesa/visualization/utils.py +3 -1
- {mesa-3.0.0a4.dist-info → mesa-3.0.0b0.dist-info}/METADATA +55 -13
- mesa-3.0.0b0.dist-info/RECORD +45 -0
- mesa-3.0.0b0.dist-info/licenses/LICENSE +202 -0
- mesa-3.0.0a4.dist-info/licenses/LICENSE → mesa-3.0.0b0.dist-info/licenses/NOTICE +2 -2
- mesa-3.0.0a4.dist-info/RECORD +0 -44
- {mesa-3.0.0a4.dist-info → mesa-3.0.0b0.dist-info}/WHEEL +0 -0
- {mesa-3.0.0a4.dist-info → mesa-3.0.0b0.dist-info}/entry_points.txt +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Altair components."""
|
|
2
|
+
|
|
1
3
|
import contextlib
|
|
2
4
|
|
|
3
5
|
import solara
|
|
@@ -8,6 +10,14 @@ with contextlib.suppress(ImportError):
|
|
|
8
10
|
|
|
9
11
|
@solara.component
|
|
10
12
|
def SpaceAltair(model, agent_portrayal, dependencies: list[any] | None = None):
|
|
13
|
+
"""A component that renders a Space using Altair.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
model: a model instance
|
|
17
|
+
agent_portrayal: agent portray specification
|
|
18
|
+
dependencies: optional list of dependencies (currently not used)
|
|
19
|
+
|
|
20
|
+
"""
|
|
11
21
|
space = getattr(model, "grid", None)
|
|
12
22
|
if space is None:
|
|
13
23
|
# Sometimes the space is defined as model.space instead of model.grid
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Support for using matplotlib to draw spaces."""
|
|
2
|
+
|
|
1
3
|
from collections import defaultdict
|
|
2
4
|
|
|
3
5
|
import networkx as nx
|
|
@@ -11,6 +13,14 @@ from mesa.experimental.cell_space import VoronoiGrid
|
|
|
11
13
|
|
|
12
14
|
@solara.component
|
|
13
15
|
def SpaceMatplotlib(model, agent_portrayal, dependencies: list[any] | None = None):
|
|
16
|
+
"""A component for rendering a space using Matplotlib.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
model: a model instance
|
|
20
|
+
agent_portrayal: a specification of how to portray an agent.
|
|
21
|
+
dependencies: list of dependencies.
|
|
22
|
+
|
|
23
|
+
"""
|
|
14
24
|
space_fig = Figure()
|
|
15
25
|
space_ax = space_fig.subplots()
|
|
16
26
|
space = getattr(model, "grid", None)
|
|
@@ -205,6 +215,14 @@ def _draw_voronoi(space, space_ax, agent_portrayal):
|
|
|
205
215
|
|
|
206
216
|
@solara.component
|
|
207
217
|
def PlotMatplotlib(model, measure, dependencies: list[any] | None = None):
|
|
218
|
+
"""A solara component for creating a matplotlib figure.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
model: Model instance
|
|
222
|
+
measure: measure to plot
|
|
223
|
+
dependencies: list of additional dependencies
|
|
224
|
+
|
|
225
|
+
"""
|
|
208
226
|
fig = Figure()
|
|
209
227
|
ax = fig.subplots()
|
|
210
228
|
df = model.datacollector.get_model_vars_dataframe()
|
|
@@ -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):
|
|
12
|
+
"""Epstein Agent."""
|
|
13
|
+
|
|
10
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
|
+
"""
|
|
11
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:
|
|
@@ -55,10 +68,13 @@ class Citizen(EpsteinAgent):
|
|
|
55
68
|
threshold,
|
|
56
69
|
arrest_prob_constant,
|
|
57
70
|
):
|
|
58
|
-
"""
|
|
59
|
-
|
|
71
|
+
"""Create a new Citizen.
|
|
72
|
+
|
|
60
73
|
Args:
|
|
61
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
|
|
62
78
|
hardship: Agent's 'perceived hardship (i.e., physical or economic
|
|
63
79
|
privation).' Exogenous, drawn from U(0,1).
|
|
64
80
|
regime_legitimacy: Agent's perception of regime legitimacy, equal
|
|
@@ -66,8 +82,8 @@ class Citizen(EpsteinAgent):
|
|
|
66
82
|
risk_aversion: Exogenous, drawn from U(0,1).
|
|
67
83
|
threshold: if (grievance - (risk_aversion * arrest_probability)) >
|
|
68
84
|
threshold, go/remain Active
|
|
69
|
-
|
|
70
|
-
|
|
85
|
+
arrest_prob_constant : agent's assessment of arrest probability
|
|
86
|
+
|
|
71
87
|
"""
|
|
72
88
|
super().__init__(model, vision, movement)
|
|
73
89
|
self.hardship = hardship
|
|
@@ -80,9 +96,7 @@ class Citizen(EpsteinAgent):
|
|
|
80
96
|
self.arrest_prob_constant = arrest_prob_constant
|
|
81
97
|
|
|
82
98
|
def step(self):
|
|
83
|
-
"""
|
|
84
|
-
Decide whether to activate, then move if applicable.
|
|
85
|
-
"""
|
|
99
|
+
"""Decide whether to activate, then move if applicable."""
|
|
86
100
|
self.update_neighbors()
|
|
87
101
|
self.update_estimated_arrest_probability()
|
|
88
102
|
net_risk = self.risk_aversion * self.arrest_probability
|
|
@@ -95,9 +109,7 @@ class Citizen(EpsteinAgent):
|
|
|
95
109
|
self.model.grid.move_agent(self, new_pos)
|
|
96
110
|
|
|
97
111
|
def update_neighbors(self):
|
|
98
|
-
"""
|
|
99
|
-
Look around and see who my neighbors are
|
|
100
|
-
"""
|
|
112
|
+
"""Look around and see who my neighbors are."""
|
|
101
113
|
self.neighborhood = self.model.grid.get_neighborhood(
|
|
102
114
|
self.pos, moore=True, radius=self.vision
|
|
103
115
|
)
|
|
@@ -107,10 +119,7 @@ class Citizen(EpsteinAgent):
|
|
|
107
119
|
]
|
|
108
120
|
|
|
109
121
|
def update_estimated_arrest_probability(self):
|
|
110
|
-
"""
|
|
111
|
-
Based on the ratio of cops to actives in my neighborhood, estimate the
|
|
112
|
-
p(Arrest | I go active).
|
|
113
|
-
"""
|
|
122
|
+
"""Based on the ratio of cops to actives in my neighborhood, estimate the p(Arrest | I go active)."""
|
|
114
123
|
cops_in_vision = len([c for c in self.neighbors if isinstance(c, Cop)])
|
|
115
124
|
actives_in_vision = 1.0 # citizen counts herself
|
|
116
125
|
for c in self.neighbors:
|
|
@@ -121,18 +130,25 @@ class Citizen(EpsteinAgent):
|
|
|
121
130
|
)
|
|
122
131
|
|
|
123
132
|
def sent_to_jail(self, value):
|
|
133
|
+
"""Sent agent to jail.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
value: duration of jail sentence
|
|
137
|
+
|
|
138
|
+
"""
|
|
124
139
|
self.model.active_agents.remove(self)
|
|
125
140
|
self.condition = AgentState.ARRESTED
|
|
126
141
|
self.model.simulator.schedule_event_relative(self.release_from_jail, value)
|
|
127
142
|
|
|
128
143
|
def release_from_jail(self):
|
|
144
|
+
"""Release agent from jail."""
|
|
129
145
|
self.model.active_agents.add(self)
|
|
130
146
|
self.condition = AgentState.QUIESCENT
|
|
131
147
|
|
|
132
148
|
|
|
133
149
|
class Cop(EpsteinAgent):
|
|
134
|
-
"""
|
|
135
|
-
|
|
150
|
+
"""A cop for life. No defection.
|
|
151
|
+
|
|
136
152
|
Summary of rule: Inspect local vision and arrest a random active agent.
|
|
137
153
|
|
|
138
154
|
Attributes:
|
|
@@ -143,14 +159,19 @@ class Cop(EpsteinAgent):
|
|
|
143
159
|
"""
|
|
144
160
|
|
|
145
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
|
+
"""
|
|
146
170
|
super().__init__(model, vision, movement)
|
|
147
171
|
self.max_jail_term = max_jail_term
|
|
148
172
|
|
|
149
173
|
def step(self):
|
|
150
|
-
"""
|
|
151
|
-
Inspect local vision and arrest a random active agent. Move if
|
|
152
|
-
applicable.
|
|
153
|
-
"""
|
|
174
|
+
"""Inspect local vision and arrest a random active agent. Move if applicable."""
|
|
154
175
|
self.update_neighbors()
|
|
155
176
|
active_neighbors = []
|
|
156
177
|
for agent in self.neighbors:
|
|
@@ -164,9 +185,7 @@ class Cop(EpsteinAgent):
|
|
|
164
185
|
self.model.grid.move_agent(self, new_pos)
|
|
165
186
|
|
|
166
187
|
def update_neighbors(self):
|
|
167
|
-
"""
|
|
168
|
-
Look around and see who my neighbors are.
|
|
169
|
-
"""
|
|
188
|
+
"""Look around and see who my neighbors are."""
|
|
170
189
|
self.neighborhood = self.model.grid.get_neighborhood(
|
|
171
190
|
self.pos, moore=True, radius=self.vision
|
|
172
191
|
)
|
|
@@ -177,9 +196,8 @@ class Cop(EpsteinAgent):
|
|
|
177
196
|
|
|
178
197
|
|
|
179
198
|
class EpsteinCivilViolence(Model):
|
|
180
|
-
"""
|
|
181
|
-
|
|
182
|
-
approach," by Joshua Epstein.
|
|
199
|
+
"""Model 1 from "Modeling civil violence: An agent-based computational approach," by Joshua Epstein.
|
|
200
|
+
|
|
183
201
|
http://www.pnas.org/content/99/suppl_3/7243.full
|
|
184
202
|
Attributes:
|
|
185
203
|
height: grid height
|
|
@@ -218,6 +236,23 @@ class EpsteinCivilViolence(Model):
|
|
|
218
236
|
max_iters=1000,
|
|
219
237
|
seed=None,
|
|
220
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
|
+
"""
|
|
221
256
|
super().__init__(seed)
|
|
222
257
|
if cop_density + citizen_density > 1:
|
|
223
258
|
raise ValueError("Cop density + citizen density must be less than 1")
|
|
@@ -257,7 +292,8 @@ class EpsteinCivilViolence(Model):
|
|
|
257
292
|
self.active_agents = self.agents
|
|
258
293
|
|
|
259
294
|
def step(self):
|
|
260
|
-
|
|
295
|
+
"""Run one step of the model."""
|
|
296
|
+
self.active_agents.shuffle_do("step")
|
|
261
297
|
|
|
262
298
|
|
|
263
299
|
if __name__ == "__main__":
|
|
@@ -1,20 +1,23 @@
|
|
|
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
|
|
4
|
+
from mesa.experimental.cell_space import FixedAgent
|
|
13
5
|
from mesa.experimental.devs.simulator import ABMSimulator
|
|
14
6
|
|
|
15
7
|
|
|
16
8
|
class Animal(mesa.Agent):
|
|
9
|
+
"""Base Animal class."""
|
|
10
|
+
|
|
17
11
|
def __init__(self, model, moore, energy, p_reproduce, energy_from_food):
|
|
12
|
+
"""Initialize Animal instance.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
model: a model instance
|
|
16
|
+
moore: using moore grid or not
|
|
17
|
+
energy: initial energy
|
|
18
|
+
p_reproduce: probability of reproduction
|
|
19
|
+
energy_from_food: energy gained from 1 unit of food
|
|
20
|
+
"""
|
|
18
21
|
super().__init__(model)
|
|
19
22
|
self.energy = energy
|
|
20
23
|
self.p_reproduce = p_reproduce
|
|
@@ -22,12 +25,14 @@ class Animal(mesa.Agent):
|
|
|
22
25
|
self.moore = moore
|
|
23
26
|
|
|
24
27
|
def random_move(self):
|
|
28
|
+
"""Move to random neighboring cell."""
|
|
25
29
|
next_moves = self.model.grid.get_neighborhood(self.pos, self.moore, True)
|
|
26
30
|
next_move = self.random.choice(next_moves)
|
|
27
31
|
# Now move:
|
|
28
32
|
self.model.grid.move_agent(self, next_move)
|
|
29
33
|
|
|
30
34
|
def spawn_offspring(self):
|
|
35
|
+
"""Create offspring."""
|
|
31
36
|
self.energy /= 2
|
|
32
37
|
offspring = self.__class__(
|
|
33
38
|
self.model,
|
|
@@ -38,13 +43,15 @@ class Animal(mesa.Agent):
|
|
|
38
43
|
)
|
|
39
44
|
self.model.grid.place_agent(offspring, self.pos)
|
|
40
45
|
|
|
41
|
-
def feed(self): ...
|
|
46
|
+
def feed(self): ... # noqa: D102
|
|
42
47
|
|
|
43
48
|
def die(self):
|
|
49
|
+
"""Die."""
|
|
44
50
|
self.model.grid.remove_agent(self)
|
|
45
51
|
self.remove()
|
|
46
52
|
|
|
47
53
|
def step(self):
|
|
54
|
+
"""Execute one step of the agent."""
|
|
48
55
|
self.random_move()
|
|
49
56
|
self.energy -= 1
|
|
50
57
|
|
|
@@ -57,13 +64,10 @@ class Animal(mesa.Agent):
|
|
|
57
64
|
|
|
58
65
|
|
|
59
66
|
class Sheep(Animal):
|
|
60
|
-
"""
|
|
61
|
-
A sheep that walks around, reproduces (asexually) and gets eaten.
|
|
62
|
-
|
|
63
|
-
The init is the same as the RandomWalker.
|
|
64
|
-
"""
|
|
67
|
+
"""A sheep that walks around, reproduces (asexually) and gets eaten."""
|
|
65
68
|
|
|
66
69
|
def feed(self):
|
|
70
|
+
"""Eat grass and gain energy."""
|
|
67
71
|
# If there is grass available, eat it
|
|
68
72
|
agents = self.model.grid.get_cell_list_contents(self.pos)
|
|
69
73
|
grass_patch = next(obj for obj in agents if isinstance(obj, GrassPatch))
|
|
@@ -73,11 +77,10 @@ class Sheep(Animal):
|
|
|
73
77
|
|
|
74
78
|
|
|
75
79
|
class Wolf(Animal):
|
|
76
|
-
"""
|
|
77
|
-
A wolf that walks around, reproduces (asexually) and eats sheep.
|
|
78
|
-
"""
|
|
80
|
+
"""A wolf that walks around, reproduces (asexually) and eats sheep."""
|
|
79
81
|
|
|
80
82
|
def feed(self):
|
|
83
|
+
"""Eat wolf and gain energy."""
|
|
81
84
|
agents = self.model.grid.get_cell_list_contents(self.pos)
|
|
82
85
|
sheep = [obj for obj in agents if isinstance(obj, Sheep)]
|
|
83
86
|
if len(sheep) > 0:
|
|
@@ -88,13 +91,11 @@ class Wolf(Animal):
|
|
|
88
91
|
sheep_to_eat.die()
|
|
89
92
|
|
|
90
93
|
|
|
91
|
-
class GrassPatch(
|
|
92
|
-
"""
|
|
93
|
-
A patch of grass that grows at a fixed rate and it is eaten by sheep
|
|
94
|
-
"""
|
|
94
|
+
class GrassPatch(FixedAgent):
|
|
95
|
+
"""A patch of grass that grows at a fixed rate and it is eaten by sheep."""
|
|
95
96
|
|
|
96
97
|
@property
|
|
97
|
-
def fully_grown(self) -> bool:
|
|
98
|
+
def fully_grown(self) -> bool: # noqa: D102
|
|
98
99
|
return self._fully_grown
|
|
99
100
|
|
|
100
101
|
@fully_grown.setter
|
|
@@ -109,12 +110,13 @@ class GrassPatch(mesa.Agent):
|
|
|
109
110
|
)
|
|
110
111
|
|
|
111
112
|
def __init__(self, model, fully_grown, countdown, grass_regrowth_time):
|
|
112
|
-
"""
|
|
113
|
-
Creates a new patch of grass
|
|
113
|
+
"""Creates a new patch of grass.
|
|
114
114
|
|
|
115
115
|
Args:
|
|
116
|
-
|
|
116
|
+
model: a model instance
|
|
117
|
+
fully_grown: (boolean) Whether the patch of grass is fully grown or not
|
|
117
118
|
countdown: Time for the patch of grass to be fully grown again
|
|
119
|
+
grass_regrowth_time: regrowth time for the grass
|
|
118
120
|
"""
|
|
119
121
|
super().__init__(model)
|
|
120
122
|
self._fully_grown = fully_grown
|
|
@@ -125,13 +127,12 @@ class GrassPatch(mesa.Agent):
|
|
|
125
127
|
setattr, countdown, function_args=[self, "fully_grown", True]
|
|
126
128
|
)
|
|
127
129
|
|
|
128
|
-
def set_fully_grown(self):
|
|
130
|
+
def set_fully_grown(self): # noqa
|
|
129
131
|
self.fully_grown = True
|
|
130
132
|
|
|
131
133
|
|
|
132
134
|
class WolfSheep(mesa.Model):
|
|
133
|
-
"""
|
|
134
|
-
Wolf-Sheep Predation Model
|
|
135
|
+
"""Wolf-Sheep Predation Model.
|
|
135
136
|
|
|
136
137
|
A model for simulating wolf and sheep (predator-prey) ecosystem modelling.
|
|
137
138
|
"""
|
|
@@ -151,10 +152,11 @@ class WolfSheep(mesa.Model):
|
|
|
151
152
|
simulator=None,
|
|
152
153
|
seed=None,
|
|
153
154
|
):
|
|
154
|
-
"""
|
|
155
|
-
Create a new Wolf-Sheep model with the given parameters.
|
|
155
|
+
"""Create a new Wolf-Sheep model with the given parameters.
|
|
156
156
|
|
|
157
157
|
Args:
|
|
158
|
+
height: height of the grid
|
|
159
|
+
width: width of the grid
|
|
158
160
|
initial_sheep: Number of sheep to start with
|
|
159
161
|
initial_wolves: Number of wolves to start with
|
|
160
162
|
sheep_reproduce: Probability of each sheep reproducing each step
|
|
@@ -164,7 +166,9 @@ class WolfSheep(mesa.Model):
|
|
|
164
166
|
grass_regrowth_time: How long it takes for a grass patch to regrow
|
|
165
167
|
once it is eaten
|
|
166
168
|
sheep_gain_from_food: Energy sheep gain from grass, if enabled.
|
|
167
|
-
moore:
|
|
169
|
+
moore: whether to use moore or von Neumann grid
|
|
170
|
+
simulator: Simulator to use for simulating wolf and sheep
|
|
171
|
+
seed: Random seed
|
|
168
172
|
"""
|
|
169
173
|
super().__init__(seed=seed)
|
|
170
174
|
# Set parameters
|
|
@@ -226,8 +230,9 @@ class WolfSheep(mesa.Model):
|
|
|
226
230
|
self.grid.place_agent(patch, pos)
|
|
227
231
|
|
|
228
232
|
def step(self):
|
|
229
|
-
|
|
230
|
-
self.agents_by_type[
|
|
233
|
+
"""Perform one step of the model."""
|
|
234
|
+
self.agents_by_type[Sheep].shuffle_do("step")
|
|
235
|
+
self.agents_by_type[Wolf].shuffle_do("step")
|
|
231
236
|
|
|
232
237
|
|
|
233
238
|
if __name__ == "__main__":
|