0
# Core Agent-Based Modeling
1
2
The core of Mesa consists of three fundamental classes that form the backbone of any agent-based model: `Agent`, `AgentSet`, and `Model`. These classes provide the essential infrastructure for creating, managing, and running agent-based simulations.
3
4
## Imports
5
6
```python { .api }
7
from mesa import Agent, Model
8
# AgentSet is accessible via Agent.create_agents() or Model.agents
9
from typing import Any, Callable, Iterable, Iterator, Sequence
10
from random import Random
11
import numpy as np
12
13
# Type aliases
14
SeedLike = int | np.integer | Sequence[int] | np.random.SeedSequence
15
RNGLike = np.random.Generator | np.random.BitGenerator
16
```
17
18
## Agent Class
19
20
The `Agent` class serves as the base class for all agents in a Mesa model. It provides essential functionality for agent lifecycle management, random number generation, and integration with the model framework.
21
22
```python { .api }
23
class Agent:
24
"""
25
Base class for model agents with lifecycle management and random number generation.
26
27
Attributes:
28
model: Reference to the model instance containing this agent
29
unique_id: Unique identifier for the agent within the model
30
pos: Position of the agent in space (if applicable)
31
"""
32
33
def __init__(self, model: Model, *args, **kwargs) -> None:
34
"""
35
Initialize a new agent.
36
37
Parameters:
38
model: The model instance this agent belongs to
39
*args: Additional positional arguments
40
**kwargs: Additional keyword arguments
41
"""
42
...
43
44
def step(self) -> None:
45
"""
46
Execute one step of agent behavior.
47
48
This method should be implemented by subclasses to define
49
the agent's behavior during each simulation step.
50
"""
51
...
52
53
def advance(self) -> None:
54
"""
55
Execute the advance phase of agent behavior.
56
57
Used in staged activation where agents first step(),
58
then advance() to update their state based on all
59
agents' step() results.
60
"""
61
...
62
63
def remove(self) -> None:
64
"""
65
Remove this agent from the model.
66
67
This properly deregisters the agent from the model
68
and handles cleanup of spatial positioning if applicable.
69
"""
70
...
71
72
@classmethod
73
def create_agents(cls, model: Model, n: int, *args, **kwargs) -> AgentSet[Agent]:
74
"""
75
Create multiple agents of this class.
76
77
Parameters:
78
model: The model instance
79
n: Number of agents to create
80
*args: Additional positional arguments for agent initialization
81
**kwargs: Additional keyword arguments for agent initialization
82
83
Returns:
84
AgentSet containing the newly created agents
85
"""
86
...
87
88
@property
89
def random(self) -> Random:
90
"""
91
Access to the model's seeded Python random number generator.
92
93
Returns:
94
The model's Random instance for reproducible randomness
95
"""
96
...
97
98
@property
99
def rng(self) -> np.random.Generator:
100
"""
101
Access to the model's seeded NumPy random number generator.
102
103
Returns:
104
The model's numpy.random.Generator instance
105
"""
106
...
107
```
108
109
### Agent Usage Examples
110
111
```python { .api }
112
from mesa import Agent, Model
113
114
class Predator(Agent):
115
"""Example predator agent with energy-based behavior."""
116
117
def __init__(self, model, energy=100):
118
super().__init__(model)
119
self.energy = energy
120
self.max_energy = energy * 2
121
122
def step(self):
123
"""Hunt for prey and consume energy."""
124
# Move randomly
125
self.move_randomly()
126
127
# Hunt for prey
128
prey_in_cell = [agent for agent in self.model.space.get_cell_list_contents([self.pos])
129
if isinstance(agent, Prey)]
130
131
if prey_in_cell and self.energy < self.max_energy:
132
prey = self.random.choice(prey_in_cell)
133
prey.remove()
134
self.energy += 50
135
136
# Consume energy
137
self.energy -= 2
138
139
# Die if energy is depleted
140
if self.energy <= 0:
141
self.remove()
142
143
def move_randomly(self):
144
"""Move to a random neighboring cell."""
145
if hasattr(self.model, 'space'):
146
neighbors = self.model.space.get_neighborhood(self.pos)
147
new_pos = self.random.choice(neighbors)
148
self.model.space.move_agent(self, new_pos)
149
150
# Creating agents
151
model = Model()
152
predator = Predator(model, energy=150)
153
154
# Creating multiple agents
155
predators = Predator.create_agents(model, n=10, energy=100)
156
```
157
158
## AgentSet Class
159
160
`AgentSet` is a powerful collection class for managing groups of agents. It provides efficient operations for filtering, sorting, and executing methods across multiple agents.
161
162
```python { .api }
163
class AgentSet(MutableSet, Sequence):
164
"""
165
Collection class for managing ordered sets of agents with advanced operations.
166
167
AgentSet implements both MutableSet and Sequence interfaces, providing
168
set-like operations while maintaining agent ordering.
169
"""
170
171
def __init__(self, agents: Iterable[Agent], random: Random | None = None):
172
"""
173
Initialize an AgentSet with agents.
174
175
Parameters:
176
agents: Iterable of agents to include in the set
177
random: Random number generator for shuffle operations
178
"""
179
...
180
181
def __len__(self) -> int:
182
"""Return the number of agents in the set."""
183
...
184
185
def __iter__(self) -> Iterator[Agent]:
186
"""Iterate over agents in the set."""
187
...
188
189
def __contains__(self, agent: Agent) -> bool:
190
"""Check if an agent is in the set."""
191
...
192
193
def __getitem__(self, item: int | slice) -> Agent:
194
"""Get agent by index or slice."""
195
...
196
197
def select(self,
198
filter_func: Callable[[Agent], bool] | None = None,
199
at_most: int | float = float("inf"),
200
inplace: bool = False,
201
agent_type: type[Agent] | None = None) -> AgentSet:
202
"""
203
Select agents based on criteria.
204
205
Parameters:
206
filter_func: Function that returns True for agents to include
207
at_most: Maximum number of agents to select
208
inplace: Whether to modify this AgentSet or return a new one
209
agent_type: Filter by agent type/class
210
211
Returns:
212
AgentSet containing selected agents (or self if inplace=True)
213
"""
214
...
215
216
def shuffle(self, inplace: bool = False) -> AgentSet:
217
"""
218
Shuffle the order of agents.
219
220
Parameters:
221
inplace: Whether to modify this AgentSet or return a new one
222
223
Returns:
224
Shuffled AgentSet (or self if inplace=True)
225
"""
226
...
227
228
def sort(self,
229
key: Callable[[Agent], Any] | str,
230
ascending: bool = False,
231
inplace: bool = False) -> AgentSet:
232
"""
233
Sort agents by a key function or attribute name.
234
235
Parameters:
236
key: Function to generate sort key or attribute name
237
ascending: Whether to sort in ascending order
238
inplace: Whether to modify this AgentSet or return a new one
239
240
Returns:
241
Sorted AgentSet (or self if inplace=True)
242
"""
243
...
244
245
def do(self, method: str | Callable, *args, **kwargs) -> AgentSet:
246
"""
247
Execute a method on all agents in the set.
248
249
Parameters:
250
method: Method name (string) or callable to execute
251
*args: Arguments to pass to the method
252
**kwargs: Keyword arguments to pass to the method
253
254
Returns:
255
This AgentSet for method chaining
256
"""
257
...
258
259
def shuffle_do(self, method: str | Callable, *args, **kwargs) -> AgentSet:
260
"""
261
Shuffle agents and then execute a method on all agents.
262
263
Parameters:
264
method: Method name (string) or callable to execute
265
*args: Arguments to pass to the method
266
**kwargs: Keyword arguments to pass to the method
267
268
Returns:
269
This AgentSet for method chaining
270
"""
271
...
272
273
def map(self, method: str | Callable, *args, **kwargs) -> list[Any]:
274
"""
275
Apply a method to all agents and return results.
276
277
Parameters:
278
method: Method name (string) or callable to apply
279
*args: Arguments to pass to the method
280
**kwargs: Keyword arguments to pass to the method
281
282
Returns:
283
List of results from applying method to each agent
284
"""
285
...
286
287
def agg(self, attribute: str, func: Callable | Iterable[Callable]) -> Any | list[Any]:
288
"""
289
Aggregate an attribute across all agents using one or more functions.
290
291
Parameters:
292
attribute: Name of the attribute to aggregate
293
func: Aggregation function(s) like sum, mean, max, etc.
294
295
Returns:
296
Aggregated value(s)
297
"""
298
...
299
300
def get(self,
301
attr_names: str | list[str],
302
handle_missing: str = "error",
303
default_value: Any = None) -> list[Any] | list[list[Any]]:
304
"""
305
Get attribute values from all agents.
306
307
Parameters:
308
attr_names: Attribute name(s) to retrieve
309
handle_missing: How to handle missing attributes ("error", "default", "ignore")
310
default_value: Default value when handle_missing="default"
311
312
Returns:
313
List of attribute values (or list of lists for multiple attributes)
314
"""
315
...
316
317
def set(self, attr_name: str, value: Any) -> AgentSet:
318
"""
319
Set an attribute value on all agents.
320
321
Parameters:
322
attr_name: Name of the attribute to set
323
value: Value to set (can be callable for per-agent values)
324
325
Returns:
326
This AgentSet for method chaining
327
"""
328
...
329
330
def add(self, agent: Agent):
331
"""Add an agent to the set."""
332
...
333
334
def discard(self, agent: Agent):
335
"""Remove an agent from the set if present."""
336
...
337
338
def remove(self, agent: Agent):
339
"""Remove an agent from the set (raises error if not present)."""
340
...
341
342
def groupby(self, by: Callable | str, result_type: str = "agentset") -> GroupBy:
343
"""
344
Group agents by a key function or attribute.
345
346
Parameters:
347
by: Grouping key function or attribute name
348
result_type: Type of result collections ("agentset" or "list")
349
350
Returns:
351
GroupBy object for grouped operations
352
"""
353
...
354
```
355
356
### AgentSet Usage Examples
357
358
```python { .api }
359
from mesa import Agent, Model
360
# AgentSet is accessible via Agent.create_agents() or Model.agents
361
362
class WealthAgent(Agent):
363
def __init__(self, model, wealth=100):
364
super().__init__(model)
365
self.wealth = wealth
366
self.age = 0
367
368
def step(self):
369
self.age += 1
370
# Simple wealth dynamics
371
if self.wealth > 0:
372
self.wealth += self.random.randint(-5, 10)
373
374
# Create model with agents
375
model = Model()
376
agents = WealthAgent.create_agents(model, n=100)
377
378
# Selection operations
379
wealthy_agents = model.agents.select(lambda a: a.wealth > 200)
380
young_agents = model.agents.select(lambda a: a.age < 10, at_most=20)
381
382
# Sorting operations
383
by_wealth = model.agents.sort(key="wealth", ascending=False)
384
by_age = model.agents.sort(key=lambda a: a.age)
385
386
# Bulk operations
387
model.agents.shuffle_do("step") # Randomly order, then call step() on each
388
model.agents.set("wealth", lambda a: max(0, a.wealth)) # Ensure non-negative wealth
389
390
# Aggregation operations
391
total_wealth = model.agents.agg("wealth", sum)
392
wealth_stats = model.agents.agg("wealth", [sum, len, max, min])
393
394
# Get attribute values
395
all_wealth = model.agents.get("wealth")
396
wealth_and_age = model.agents.get(["wealth", "age"])
397
398
# Grouping operations
399
by_wealth_class = model.agents.groupby(lambda a: "rich" if a.wealth > 100 else "poor")
400
rich_agents = by_wealth_class["rich"]
401
```
402
403
## GroupBy Class
404
405
The `GroupBy` class enables grouped operations on agents, similar to SQL GROUP BY or pandas groupby functionality.
406
407
```python { .api }
408
class GroupBy:
409
"""
410
Helper class for grouped operations on agents.
411
412
Provides methods to apply operations to groups of agents
413
created by AgentSet.groupby().
414
"""
415
416
def __init__(self, groups: dict[Any, list | AgentSet]):
417
"""
418
Initialize GroupBy with groups.
419
420
Parameters:
421
groups: Dictionary mapping group keys to agent collections
422
"""
423
...
424
425
def map(self, method: Callable | str, *args, **kwargs) -> dict[Any, Any]:
426
"""
427
Apply a method to each group and return results.
428
429
Parameters:
430
method: Method name or callable to apply
431
*args: Arguments to pass to the method
432
**kwargs: Keyword arguments to pass to the method
433
434
Returns:
435
Dictionary mapping group keys to results
436
"""
437
...
438
439
def do(self, method: Callable | str, *args, **kwargs) -> GroupBy:
440
"""
441
Execute a method on each group.
442
443
Parameters:
444
method: Method name or callable to execute
445
*args: Arguments to pass to the method
446
**kwargs: Keyword arguments to pass to the method
447
448
Returns:
449
This GroupBy object for method chaining
450
"""
451
...
452
453
def count(self) -> dict[Any, int]:
454
"""
455
Count agents in each group.
456
457
Returns:
458
Dictionary mapping group keys to agent counts
459
"""
460
...
461
462
def agg(self, attr_name: str, func: Callable) -> dict[Hashable, Any]:
463
"""
464
Aggregate an attribute within each group.
465
466
Parameters:
467
attr_name: Name of the attribute to aggregate
468
func: Aggregation function (sum, mean, max, etc.)
469
470
Returns:
471
Dictionary mapping group keys to aggregated values
472
"""
473
...
474
```
475
476
## Model Class
477
478
The `Model` class serves as the container and coordinator for the entire simulation. It manages agents, handles random number generation, and orchestrates the simulation loop.
479
480
```python { .api }
481
class Model:
482
"""
483
Base class for agent-based models with simulation management.
484
485
The Model class provides infrastructure for managing agents,
486
coordinating simulation steps, and handling random number generation.
487
488
Attributes:
489
running: Boolean indicating if model should continue running
490
steps: Number of times model.step() has been called
491
random: Seeded Python random number generator
492
rng: Seeded NumPy random number generator
493
"""
494
495
def __init__(self,
496
*args: Any,
497
seed: float | None = None,
498
rng: RNGLike | SeedLike | None = None,
499
**kwargs: Any) -> None:
500
"""
501
Initialize a new model.
502
503
Parameters:
504
*args: Additional positional arguments
505
seed: Random seed for reproducible results
506
rng: Random number generator or seed
507
**kwargs: Additional keyword arguments
508
"""
509
...
510
511
def step(self) -> None:
512
"""
513
Execute one step of the model.
514
515
This method should be implemented by subclasses to define
516
the model's behavior during each simulation step.
517
"""
518
...
519
520
def run_model(self) -> None:
521
"""
522
Run the model until completion.
523
524
Repeatedly calls step() while self.running is True.
525
"""
526
...
527
528
def register_agent(self, agent):
529
"""
530
Register an agent with the model.
531
532
Parameters:
533
agent: Agent to register
534
"""
535
...
536
537
def deregister_agent(self, agent):
538
"""
539
Deregister an agent from the model.
540
541
Parameters:
542
agent: Agent to deregister
543
"""
544
...
545
546
def reset_randomizer(self, seed: int | None = None) -> None:
547
"""
548
Reset the random number generators with a new seed.
549
550
Parameters:
551
seed: New random seed
552
"""
553
...
554
555
def reset_rng(self, rng: RNGLike | SeedLike | None = None) -> None:
556
"""
557
Reset the NumPy random number generator.
558
559
Parameters:
560
rng: New random number generator or seed
561
"""
562
...
563
564
def remove_all_agents(self):
565
"""Remove all agents from the model."""
566
...
567
568
@property
569
def agents(self) -> AgentSet:
570
"""
571
Get all agents in the model.
572
573
Returns:
574
AgentSet containing all registered agents
575
"""
576
...
577
578
@property
579
def agent_types(self) -> list[type]:
580
"""
581
Get all agent types present in the model.
582
583
Returns:
584
List of agent classes represented in the model
585
"""
586
...
587
588
@property
589
def agents_by_type(self) -> dict[type[Agent], AgentSet]:
590
"""
591
Get agents organized by type.
592
593
Returns:
594
Dictionary mapping agent types to AgentSets
595
"""
596
...
597
```
598
599
### Model Usage Examples
600
601
```python { .api }
602
from mesa import Agent, Model, DataCollector
603
import numpy as np
604
605
class EcosystemModel(Model):
606
"""Example ecosystem model with predators and prey."""
607
608
def __init__(self, n_predators=20, n_prey=100, width=50, height=50):
609
super().__init__()
610
self.width = width
611
self.height = height
612
613
# Create spatial environment (would typically use mesa.space or mesa.discrete_space)
614
# self.space = MultiGrid(width, height, True)
615
616
# Data collection
617
self.datacollector = DataCollector(
618
model_reporters={
619
"Predators": lambda m: len(m.agents.select(agent_type=Predator)),
620
"Prey": lambda m: len(m.agents.select(agent_type=Prey)),
621
"Total Population": lambda m: len(m.agents)
622
},
623
agent_reporters={
624
"Energy": "energy",
625
"Position": "pos"
626
}
627
)
628
629
# Create agents
630
for i in range(n_predators):
631
predator = Predator(self, energy=100)
632
633
for i in range(n_prey):
634
prey = Prey(self, energy=50)
635
636
self.running = True
637
638
def step(self):
639
"""Execute one step of the ecosystem simulation."""
640
# Collect data before step
641
self.datacollector.collect(self)
642
643
# Execute agent behaviors in random order
644
self.agents.shuffle_do("step")
645
646
# Check if simulation should continue
647
predator_count = len(self.agents.select(agent_type=Predator))
648
prey_count = len(self.agents.select(agent_type=Prey))
649
650
if predator_count == 0 or prey_count == 0:
651
self.running = False
652
653
# Running the model
654
model = EcosystemModel(n_predators=10, n_prey=50)
655
656
# Run for specific number of steps
657
for step in range(100):
658
model.step()
659
if not model.running:
660
print(f"Simulation ended at step {step}")
661
break
662
663
# Or run until completion
664
model = EcosystemModel(n_predators=10, n_prey=50)
665
model.run_model()
666
667
# Access agents by type
668
predators = model.agents_by_type[Predator]
669
prey = model.agents_by_type[Prey]
670
671
# Get model statistics
672
print(f"Final population: {len(model.agents)} agents")
673
print(f"Agent types: {model.agent_types}")
674
```
675
676
## Advanced Usage Patterns
677
678
### Custom Agent Behaviors
679
680
```python { .api }
681
class SocialAgent(Agent):
682
"""Agent with social network capabilities."""
683
684
def __init__(self, model, cooperation_probability=0.5):
685
super().__init__(model)
686
self.cooperation_prob = cooperation_probability
687
self.social_network = set()
688
self.reputation = 0.5
689
690
def add_connection(self, other_agent):
691
"""Add a social connection."""
692
self.social_network.add(other_agent)
693
other_agent.social_network.add(self)
694
695
def interact_with_neighbors(self):
696
"""Interact with agents in social network."""
697
for neighbor in self.social_network:
698
if self.random.random() < self.cooperation_prob:
699
# Cooperate
700
neighbor.reputation += 0.1
701
self.reputation += 0.05
702
else:
703
# Defect
704
neighbor.reputation -= 0.05
705
706
def update_cooperation(self):
707
"""Update cooperation probability based on reputation."""
708
self.cooperation_prob = min(1.0, max(0.0, self.reputation))
709
710
def step(self):
711
self.interact_with_neighbors()
712
self.update_cooperation()
713
```
714
715
### Staged Activation Pattern
716
717
```python { .api }
718
class StagedModel(Model):
719
"""Model using staged activation for simultaneous updates."""
720
721
def __init__(self, n_agents=100):
722
super().__init__()
723
724
# Create agents
725
for i in range(n_agents):
726
agent = SocialAgent(self)
727
728
self.running = True
729
730
def step(self):
731
"""Execute staged activation: step, then advance."""
732
# Stage 1: All agents observe and plan
733
self.agents.do("step")
734
735
# Stage 2: All agents update their state simultaneously
736
self.agents.do("advance")
737
```
738
739
## Type Definitions
740
741
```python { .api }
742
# Type aliases used in Mesa core modules
743
from typing import Any, Callable, Hashable, Iterable, Iterator, MutableSet, Sequence
744
from random import Random
745
import numpy as np
746
747
# Random number generation types
748
SeedLike = int | np.integer | Sequence[int] | np.random.SeedSequence
749
RNGLike = np.random.Generator | np.random.BitGenerator
750
751
# Agent and model types (forward references for documentation)
752
Agent = Agent
753
AgentSet = AgentSet[Agent]
754
Model = Model
755
GroupBy = GroupBy
756
```