Agent Activation#
The Boltzmann Wealth Model#
Important:
If you are just exploring Mesa and want the fastest way to execute the code we recommend executing this tutorial online in a Colab notebook.
or if you do not have a Google account you can use
(This can take 30 seconds to 5 minutes to load)
If you are running locally, please ensure you have the latest Mesa version installed.
Tutorial Description#
In the previous tutorial you learned how to query, filter, and group agents using AgentSet. Now we’ll cover how to make agents actually do things — and why the order and pattern of activation matters. By the end of this tutorial you will know how to:
Activate agents sequentially (
do) and in random order (shuffle_do)Collect return values with
mapCombine
selectwith activation for conditional executionImplement common activation patterns: simultaneous, staged, and type-based
Understand why activation order affects model outcomes
IN COLAB? - Run the next cell#
# %pip install --quiet mesa[rec]
Import Dependencies#
import numpy as np
import seaborn as sns
import mesa
do and shuffle_do: The Core Activation Methods#
AgentSet provides two primary methods for activating agents:
do(method)— Calls the named method on each agent, in the current order of the set.shuffle_do(method)— Randomly reorders agents, then calls the method on each. Both accept a method name (string) or a callable. Additional arguments are passed through to each agent’s method. Let’s see these in action with a minimal model.
class MoneyAgent(mesa.Agent):
"""An agent with fixed initial wealth."""
def __init__(self, model):
super().__init__(model)
self.wealth = 1
def exchange(self):
if self.wealth > 0:
other = self.random.choice(self.model.agents)
other.wealth += 1
self.wealth -= 1
class MoneyModel(mesa.Model):
"""A model with some number of agents."""
def __init__(self, n=10):
super().__init__()
MoneyAgent.create_agents(model=self, n=n)
def step(self):
# Random activation — each agent acts in a random order
self.agents.shuffle_do("exchange")
model = MoneyModel(10)
model.run_for(30)
wealth = model.agents.get("wealth")
print(f"Wealth distribution: {sorted(wealth, reverse=True)}")
print(f"Total wealth: {sum(wealth)} (should be {len(model.agents)})")
Wealth distribution: [4, 2, 2, 2, 0, 0, 0, 0, 0, 0]
Total wealth: 10 (should be 10)
Why Activation Order Matters#
The order in which agents act can significantly affect model outcomes. Consider a
simple scenario: if Agent A gives money to Agent B, and then Agent B gives money away,
Agent B now has more to give. If the order were reversed — B acts first, then A gives
to B — the outcome differs.
This is a well-studied phenomenon in agent-based modeling. Comer (2014) showed that
activation order can materially impact emergent behavior.
Let’s demonstrate this by comparing do (fixed order) with shuffle_do (random order).
# Fixed order — same agent always goes first
class FixedOrderModel(mesa.Model):
def __init__(self, n=10):
super().__init__()
MoneyAgent.create_agents(model=self, n=n)
def step(self):
self.agents.do("exchange") # Same order every step
# Random order — different every step
class RandomOrderModel(mesa.Model):
def __init__(self, n=10):
super().__init__()
MoneyAgent.create_agents(model=self, n=n)
def step(self):
self.agents.shuffle_do("exchange")
# Run both multiple times and compare Gini coefficients
def gini(model):
x = sorted(model.agents.get("wealth"))
n = len(x)
B = sum(xi * (n - i) for i, xi in enumerate(x)) / (n * sum(x))
return 1 + (1 / n) - 2 * B
fixed_ginis = []
random_ginis = []
for _ in range(50):
m = FixedOrderModel(50)
m.run_for(100)
fixed_ginis.append(gini(m))
m = RandomOrderModel(50)
m.run_for(100)
random_ginis.append(gini(m))
print(
f"Fixed order — mean Gini: {np.mean(fixed_ginis):.3f} (std: {np.std(fixed_ginis):.3f})"
)
print(
f"Random order — mean Gini: {np.mean(random_ginis):.3f} (std: {np.std(random_ginis):.3f})"
)
Fixed order — mean Gini: 0.618 (std: 0.041)
Random order — mean Gini: 0.613 (std: 0.040)
General guidance: Use shuffle_do unless your model specifically requires a fixed
activation order. Random activation avoids systematic biases where early-acting agents
have an inherent advantage.
Using Callables with do#
Instead of passing a method name as a string, you can pass a callable function directly. The function receives each agent as its first argument:
# Using a callable instead of a method name
def tax_agent(agent):
"""Take 10% tax from agents with wealth > 5."""
if agent.wealth > 5:
tax = agent.wealth // 10
agent.wealth -= tax
model = MoneyModel(50)
model.run_for(100)
print(f"Max wealth before tax: {model.agents.agg('wealth', max)}")
model.agents.do(tax_agent)
print(f"Max wealth after tax: {model.agents.agg('wealth', max)}")
Max wealth before tax: 8
Max wealth after tax: 8
Collecting Results with map#
While do calls a method and discards the return values, map calls a method and
returns the results as a list. Use map when each agent computes something you
need to collect.
class ReportingAgent(mesa.Agent):
def __init__(self, model):
super().__init__(model)
self.wealth = 1
self.age = self.random.randint(18, 80)
def report_status(self):
return {"id": self.unique_id, "wealth": self.wealth, "age": self.age}
def exchange(self):
if self.wealth > 0:
other = self.random.choice(self.model.agents)
other.wealth += 1
self.wealth -= 1
class ReportingModel(mesa.Model):
def __init__(self, n=10):
super().__init__()
ReportingAgent.create_agents(model=self, n=n)
def step(self):
self.agents.shuffle_do("exchange")
model = ReportingModel(10)
model.run_for(20)
# Collect status reports from all agents
reports = model.agents.map("report_status")
print("Agent reports:")
for r in reports[:5]:
print(f" {r}")
Agent reports:
{'id': 1, 'wealth': 2, 'age': 39}
{'id': 2, 'wealth': 1, 'age': 71}
{'id': 3, 'wealth': 1, 'age': 51}
{'id': 4, 'wealth': 0, 'age': 76}
{'id': 5, 'wealth': 0, 'age': 76}
You can also use map with a callable:
# Calculate each agent's wealth-to-age ratio
ratios = model.agents.map(lambda a: a.wealth / a.age)
print(f"Wealth/age ratios (first 5): {[f'{r:.3f}' for r in ratios[:5]]}")
Wealth/age ratios (first 5): ['0.051', '0.014', '0.020', '0.000', '0.000']
Conditional Activation with select + do#
One of the most powerful patterns in Mesa is combining select with activation.
By filtering agents first, you can activate only those that meet specific criteria.
In many real-world models, not all agents act every step. Maybe only agents with
sufficient energy can move, only living agents can reproduce, or only wealthy agents
pay taxes.
class MoneyAgent(mesa.Agent):
def __init__(self, model):
super().__init__(model)
self.wealth = 1
def exchange(self):
if self.wealth > 0:
other = self.random.choice(self.model.agents)
other.wealth += 1
self.wealth -= 1
def donate(self, recipients):
"""Give 1 unit to a random recipient."""
if self.wealth > 0 and len(recipients) > 0:
recipient = self.random.choice(recipients)
recipient.wealth += 1
self.wealth -= 1
class PolicyModel(mesa.Model):
"""A model where only rich agents donate to poor agents."""
def __init__(self, n=100):
super().__init__()
MoneyAgent.create_agents(model=self, n=n)
def step(self):
# First: normal exchanges
self.agents.shuffle_do("exchange")
# Then: redistribution policy — rich donate to poor
rich = self.agents.select(lambda a: a.wealth >= 5)
poor = self.agents.select(lambda a: a.wealth == 0)
if len(rich) > 0 and len(poor) > 0:
rich.shuffle_do("donate", poor)
model = PolicyModel(100)
model.run_for(100)
broke = len(model.agents.select(lambda a: a.wealth == 0))
rich = len(model.agents.select(lambda a: a.wealth >= 5))
print(f"After redistribution policy: {broke} broke agents, {rich} rich agents")
After redistribution policy: 37 broke agents, 0 rich agents
Common Activation Patterns#
Before Mesa 3.0, activation patterns were hard-coded into “scheduler” classes (RandomActivation, SimultaneousActivation, etc.). Now, you compose them directly from AgentSet methods. This is more flexible — you can mix and match any combination. Here are the most common patterns:
Sequential Activation#
Agents act in a fixed order. The simplest pattern, but can introduce systematic bias.
self.agents.do("step")
Random Activation#
Agents act in a new random order each step. The most common default.
self.agents.shuffle_do("step")
Simultaneous Activation#
All agents first compute their next state, then all advance at once. This prevents early-acting agents from influencing later ones within the same step. Classic examples include Conway’s Game of Life and Schelling’s segregation model.
self.agents.do("compute_next_state")
self.agents.do("advance")
Staged Activation#
Agents perform multiple actions per step, in a defined sequence of stages. For example, agents might first move, then eat, then reproduce.
for stage in ["move", "eat", "reproduce"]:
self.agents.shuffle_do(stage)
Type-Based Activation#
In models with multiple agent types, you often want each type to act separately.
For example, predators and prey might take turns. Use agents_by_type to access
the AgentSet for each type:
for agent_type in self.agent_types:
self.agents_by_type[agent_type].shuffle_do("step")
Or target specific types:
self.agents_by_type[Prey].shuffle_do("step")
self.agents_by_type[Predator].shuffle_do("step")
Combining Patterns#
The real power is in combining these patterns freely. Here’s an example that uses staged, type-based, and conditional activation all at once:
class Prey(mesa.Agent):
def __init__(self, model):
super().__init__(model)
self.energy = 5
def move(self):
self.energy -= 1
def eat(self):
self.energy += self.random.randint(0, 2)
def reproduce(self):
if self.energy > 8:
self.energy -= 4
Prey(self.model) # New prey is automatically registered
class Predator(mesa.Agent):
def __init__(self, model):
super().__init__(model)
self.energy = 10
self.kills = 0
def move(self):
self.energy -= 2 # Predators use more energy
def hunt(self):
prey_agents = self.model.agents_by_type.get(Prey)
if prey_agents and len(prey_agents) > 0 and self.energy > 0:
target = self.random.choice(prey_agents)
target.remove()
self.energy += 5
self.kills += 1
class EcosystemModel(mesa.Model):
def __init__(self, n_prey=50, n_predators=5):
super().__init__()
Prey.create_agents(model=self, n=n_prey)
Predator.create_agents(model=self, n=n_predators)
def step(self):
# Stage 1: All agents move (random order within each type)
for agent_type in self.agent_types:
self.agents_by_type[agent_type].shuffle_do("move")
# Stage 2: Type-specific actions
if Prey in self.agents_by_type:
self.agents_by_type[Prey].shuffle_do("eat")
if Predator in self.agents_by_type:
self.agents_by_type[Predator].shuffle_do("hunt")
# Stage 3: Only prey with enough energy reproduce
if Prey in self.agents_by_type:
fertile = self.agents_by_type[Prey].select(lambda a: a.energy > 8)
fertile.do("reproduce")
# Remove dead agents (energy depleted)
dead = self.agents.select(lambda a: a.energy <= 0)
for agent in dead:
agent.remove()
eco = EcosystemModel(50, 5)
eco.run_for(20)
n_prey = len(eco.agents_by_type.get(Prey, []))
n_pred = len(eco.agents_by_type.get(Predator, []))
print(f"After 20 steps: {n_prey} prey, {n_pred} predators, {len(eco.agents)} total")
After 20 steps: 0 prey, 5 predators, 5 total
This example demonstrates several key points:
Staged activation: All agents move first, then type-specific actions happen
Type-based activation: Prey eat while predators hunt
Conditional activation: Only fertile prey reproduce
Dynamic agent creation/removal: Prey reproduce and dead agents are removed None of this required any special scheduler class — just
do,shuffle_do, andselect, composed in the order that makes sense for your model.
Summary#
Method |
Purpose |
Returns |
|---|---|---|
|
Call method on each agent (fixed order) |
The AgentSet |
|
Call method on each agent (random order) |
The AgentSet |
|
Call method on each agent and collect results |
List of results |
Key activation patterns: |
Random:
agents.shuffle_do("step")— use this as your defaultSequential:
agents.do("step")— when order is intentionalSimultaneous:
agents.do("compute")thenagents.do("advance")Staged: loop over stages, calling
do/shuffle_dofor eachType-based: use
agents_by_type[Type].shuffle_do("step")Conditional:
agents.select(condition).do("step")Combine these freely to express exactly the activation logic your model needs.
Next Steps#
Check out the Event Scheduling & Time tutorial to learn how to schedule events at specific times, create recurring events, and control how your simulation progresses through time.
[Comer2014] Comer, Kenneth W. “Who Goes First? An Examination of the Impact of Activation on Outcome Behavior in AgentBased Models.” George Mason University, 2014. http://mars.gmu.edu/bitstream/handle/1920/9070/Comer_gmu_0883E_10539.pdf