Agent-based modeling (ABM) in Python framework with spatial grids, agent schedulers, data collection tools, and browser-based visualization capabilities
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.
from mesa import Agent, Model
# AgentSet is accessible via Agent.create_agents() or Model.agents
from typing import Any, Callable, Iterable, Iterator, Sequence
from random import Random
import numpy as np
# Type aliases
SeedLike = int | np.integer | Sequence[int] | np.random.SeedSequence
RNGLike = np.random.Generator | np.random.BitGeneratorThe 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.
class Agent:
"""
Base class for model agents with lifecycle management and random number generation.
Attributes:
model: Reference to the model instance containing this agent
unique_id: Unique identifier for the agent within the model
pos: Position of the agent in space (if applicable)
"""
def __init__(self, model: Model, *args, **kwargs) -> None:
"""
Initialize a new agent.
Parameters:
model: The model instance this agent belongs to
*args: Additional positional arguments
**kwargs: Additional keyword arguments
"""
...
def step(self) -> None:
"""
Execute one step of agent behavior.
This method should be implemented by subclasses to define
the agent's behavior during each simulation step.
"""
...
def advance(self) -> None:
"""
Execute the advance phase of agent behavior.
Used in staged activation where agents first step(),
then advance() to update their state based on all
agents' step() results.
"""
...
def remove(self) -> None:
"""
Remove this agent from the model.
This properly deregisters the agent from the model
and handles cleanup of spatial positioning if applicable.
"""
...
@classmethod
def create_agents(cls, model: Model, n: int, *args, **kwargs) -> AgentSet[Agent]:
"""
Create multiple agents of this class.
Parameters:
model: The model instance
n: Number of agents to create
*args: Additional positional arguments for agent initialization
**kwargs: Additional keyword arguments for agent initialization
Returns:
AgentSet containing the newly created agents
"""
...
@property
def random(self) -> Random:
"""
Access to the model's seeded Python random number generator.
Returns:
The model's Random instance for reproducible randomness
"""
...
@property
def rng(self) -> np.random.Generator:
"""
Access to the model's seeded NumPy random number generator.
Returns:
The model's numpy.random.Generator instance
"""
...from mesa import Agent, Model
class Predator(Agent):
"""Example predator agent with energy-based behavior."""
def __init__(self, model, energy=100):
super().__init__(model)
self.energy = energy
self.max_energy = energy * 2
def step(self):
"""Hunt for prey and consume energy."""
# Move randomly
self.move_randomly()
# Hunt for prey
prey_in_cell = [agent for agent in self.model.space.get_cell_list_contents([self.pos])
if isinstance(agent, Prey)]
if prey_in_cell and self.energy < self.max_energy:
prey = self.random.choice(prey_in_cell)
prey.remove()
self.energy += 50
# Consume energy
self.energy -= 2
# Die if energy is depleted
if self.energy <= 0:
self.remove()
def move_randomly(self):
"""Move to a random neighboring cell."""
if hasattr(self.model, 'space'):
neighbors = self.model.space.get_neighborhood(self.pos)
new_pos = self.random.choice(neighbors)
self.model.space.move_agent(self, new_pos)
# Creating agents
model = Model()
predator = Predator(model, energy=150)
# Creating multiple agents
predators = Predator.create_agents(model, n=10, energy=100)AgentSet is a powerful collection class for managing groups of agents. It provides efficient operations for filtering, sorting, and executing methods across multiple agents.
class AgentSet(MutableSet, Sequence):
"""
Collection class for managing ordered sets of agents with advanced operations.
AgentSet implements both MutableSet and Sequence interfaces, providing
set-like operations while maintaining agent ordering.
"""
def __init__(self, agents: Iterable[Agent], random: Random | None = None):
"""
Initialize an AgentSet with agents.
Parameters:
agents: Iterable of agents to include in the set
random: Random number generator for shuffle operations
"""
...
def __len__(self) -> int:
"""Return the number of agents in the set."""
...
def __iter__(self) -> Iterator[Agent]:
"""Iterate over agents in the set."""
...
def __contains__(self, agent: Agent) -> bool:
"""Check if an agent is in the set."""
...
def __getitem__(self, item: int | slice) -> Agent:
"""Get agent by index or slice."""
...
def select(self,
filter_func: Callable[[Agent], bool] | None = None,
at_most: int | float = float("inf"),
inplace: bool = False,
agent_type: type[Agent] | None = None) -> AgentSet:
"""
Select agents based on criteria.
Parameters:
filter_func: Function that returns True for agents to include
at_most: Maximum number of agents to select
inplace: Whether to modify this AgentSet or return a new one
agent_type: Filter by agent type/class
Returns:
AgentSet containing selected agents (or self if inplace=True)
"""
...
def shuffle(self, inplace: bool = False) -> AgentSet:
"""
Shuffle the order of agents.
Parameters:
inplace: Whether to modify this AgentSet or return a new one
Returns:
Shuffled AgentSet (or self if inplace=True)
"""
...
def sort(self,
key: Callable[[Agent], Any] | str,
ascending: bool = False,
inplace: bool = False) -> AgentSet:
"""
Sort agents by a key function or attribute name.
Parameters:
key: Function to generate sort key or attribute name
ascending: Whether to sort in ascending order
inplace: Whether to modify this AgentSet or return a new one
Returns:
Sorted AgentSet (or self if inplace=True)
"""
...
def do(self, method: str | Callable, *args, **kwargs) -> AgentSet:
"""
Execute a method on all agents in the set.
Parameters:
method: Method name (string) or callable to execute
*args: Arguments to pass to the method
**kwargs: Keyword arguments to pass to the method
Returns:
This AgentSet for method chaining
"""
...
def shuffle_do(self, method: str | Callable, *args, **kwargs) -> AgentSet:
"""
Shuffle agents and then execute a method on all agents.
Parameters:
method: Method name (string) or callable to execute
*args: Arguments to pass to the method
**kwargs: Keyword arguments to pass to the method
Returns:
This AgentSet for method chaining
"""
...
def map(self, method: str | Callable, *args, **kwargs) -> list[Any]:
"""
Apply a method to all agents and return results.
Parameters:
method: Method name (string) or callable to apply
*args: Arguments to pass to the method
**kwargs: Keyword arguments to pass to the method
Returns:
List of results from applying method to each agent
"""
...
def agg(self, attribute: str, func: Callable | Iterable[Callable]) -> Any | list[Any]:
"""
Aggregate an attribute across all agents using one or more functions.
Parameters:
attribute: Name of the attribute to aggregate
func: Aggregation function(s) like sum, mean, max, etc.
Returns:
Aggregated value(s)
"""
...
def get(self,
attr_names: str | list[str],
handle_missing: str = "error",
default_value: Any = None) -> list[Any] | list[list[Any]]:
"""
Get attribute values from all agents.
Parameters:
attr_names: Attribute name(s) to retrieve
handle_missing: How to handle missing attributes ("error", "default", "ignore")
default_value: Default value when handle_missing="default"
Returns:
List of attribute values (or list of lists for multiple attributes)
"""
...
def set(self, attr_name: str, value: Any) -> AgentSet:
"""
Set an attribute value on all agents.
Parameters:
attr_name: Name of the attribute to set
value: Value to set (can be callable for per-agent values)
Returns:
This AgentSet for method chaining
"""
...
def add(self, agent: Agent):
"""Add an agent to the set."""
...
def discard(self, agent: Agent):
"""Remove an agent from the set if present."""
...
def remove(self, agent: Agent):
"""Remove an agent from the set (raises error if not present)."""
...
def groupby(self, by: Callable | str, result_type: str = "agentset") -> GroupBy:
"""
Group agents by a key function or attribute.
Parameters:
by: Grouping key function or attribute name
result_type: Type of result collections ("agentset" or "list")
Returns:
GroupBy object for grouped operations
"""
...from mesa import Agent, Model
# AgentSet is accessible via Agent.create_agents() or Model.agents
class WealthAgent(Agent):
def __init__(self, model, wealth=100):
super().__init__(model)
self.wealth = wealth
self.age = 0
def step(self):
self.age += 1
# Simple wealth dynamics
if self.wealth > 0:
self.wealth += self.random.randint(-5, 10)
# Create model with agents
model = Model()
agents = WealthAgent.create_agents(model, n=100)
# Selection operations
wealthy_agents = model.agents.select(lambda a: a.wealth > 200)
young_agents = model.agents.select(lambda a: a.age < 10, at_most=20)
# Sorting operations
by_wealth = model.agents.sort(key="wealth", ascending=False)
by_age = model.agents.sort(key=lambda a: a.age)
# Bulk operations
model.agents.shuffle_do("step") # Randomly order, then call step() on each
model.agents.set("wealth", lambda a: max(0, a.wealth)) # Ensure non-negative wealth
# Aggregation operations
total_wealth = model.agents.agg("wealth", sum)
wealth_stats = model.agents.agg("wealth", [sum, len, max, min])
# Get attribute values
all_wealth = model.agents.get("wealth")
wealth_and_age = model.agents.get(["wealth", "age"])
# Grouping operations
by_wealth_class = model.agents.groupby(lambda a: "rich" if a.wealth > 100 else "poor")
rich_agents = by_wealth_class["rich"]The GroupBy class enables grouped operations on agents, similar to SQL GROUP BY or pandas groupby functionality.
class GroupBy:
"""
Helper class for grouped operations on agents.
Provides methods to apply operations to groups of agents
created by AgentSet.groupby().
"""
def __init__(self, groups: dict[Any, list | AgentSet]):
"""
Initialize GroupBy with groups.
Parameters:
groups: Dictionary mapping group keys to agent collections
"""
...
def map(self, method: Callable | str, *args, **kwargs) -> dict[Any, Any]:
"""
Apply a method to each group and return results.
Parameters:
method: Method name or callable to apply
*args: Arguments to pass to the method
**kwargs: Keyword arguments to pass to the method
Returns:
Dictionary mapping group keys to results
"""
...
def do(self, method: Callable | str, *args, **kwargs) -> GroupBy:
"""
Execute a method on each group.
Parameters:
method: Method name or callable to execute
*args: Arguments to pass to the method
**kwargs: Keyword arguments to pass to the method
Returns:
This GroupBy object for method chaining
"""
...
def count(self) -> dict[Any, int]:
"""
Count agents in each group.
Returns:
Dictionary mapping group keys to agent counts
"""
...
def agg(self, attr_name: str, func: Callable) -> dict[Hashable, Any]:
"""
Aggregate an attribute within each group.
Parameters:
attr_name: Name of the attribute to aggregate
func: Aggregation function (sum, mean, max, etc.)
Returns:
Dictionary mapping group keys to aggregated values
"""
...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.
class Model:
"""
Base class for agent-based models with simulation management.
The Model class provides infrastructure for managing agents,
coordinating simulation steps, and handling random number generation.
Attributes:
running: Boolean indicating if model should continue running
steps: Number of times model.step() has been called
random: Seeded Python random number generator
rng: Seeded NumPy random number generator
"""
def __init__(self,
*args: Any,
seed: float | None = None,
rng: RNGLike | SeedLike | None = None,
**kwargs: Any) -> None:
"""
Initialize a new model.
Parameters:
*args: Additional positional arguments
seed: Random seed for reproducible results
rng: Random number generator or seed
**kwargs: Additional keyword arguments
"""
...
def step(self) -> None:
"""
Execute one step of the model.
This method should be implemented by subclasses to define
the model's behavior during each simulation step.
"""
...
def run_model(self) -> None:
"""
Run the model until completion.
Repeatedly calls step() while self.running is True.
"""
...
def register_agent(self, agent):
"""
Register an agent with the model.
Parameters:
agent: Agent to register
"""
...
def deregister_agent(self, agent):
"""
Deregister an agent from the model.
Parameters:
agent: Agent to deregister
"""
...
def reset_randomizer(self, seed: int | None = None) -> None:
"""
Reset the random number generators with a new seed.
Parameters:
seed: New random seed
"""
...
def reset_rng(self, rng: RNGLike | SeedLike | None = None) -> None:
"""
Reset the NumPy random number generator.
Parameters:
rng: New random number generator or seed
"""
...
def remove_all_agents(self):
"""Remove all agents from the model."""
...
@property
def agents(self) -> AgentSet:
"""
Get all agents in the model.
Returns:
AgentSet containing all registered agents
"""
...
@property
def agent_types(self) -> list[type]:
"""
Get all agent types present in the model.
Returns:
List of agent classes represented in the model
"""
...
@property
def agents_by_type(self) -> dict[type[Agent], AgentSet]:
"""
Get agents organized by type.
Returns:
Dictionary mapping agent types to AgentSets
"""
...from mesa import Agent, Model, DataCollector
import numpy as np
class EcosystemModel(Model):
"""Example ecosystem model with predators and prey."""
def __init__(self, n_predators=20, n_prey=100, width=50, height=50):
super().__init__()
self.width = width
self.height = height
# Create spatial environment (would typically use mesa.space or mesa.discrete_space)
# self.space = MultiGrid(width, height, True)
# Data collection
self.datacollector = DataCollector(
model_reporters={
"Predators": lambda m: len(m.agents.select(agent_type=Predator)),
"Prey": lambda m: len(m.agents.select(agent_type=Prey)),
"Total Population": lambda m: len(m.agents)
},
agent_reporters={
"Energy": "energy",
"Position": "pos"
}
)
# Create agents
for i in range(n_predators):
predator = Predator(self, energy=100)
for i in range(n_prey):
prey = Prey(self, energy=50)
self.running = True
def step(self):
"""Execute one step of the ecosystem simulation."""
# Collect data before step
self.datacollector.collect(self)
# Execute agent behaviors in random order
self.agents.shuffle_do("step")
# Check if simulation should continue
predator_count = len(self.agents.select(agent_type=Predator))
prey_count = len(self.agents.select(agent_type=Prey))
if predator_count == 0 or prey_count == 0:
self.running = False
# Running the model
model = EcosystemModel(n_predators=10, n_prey=50)
# Run for specific number of steps
for step in range(100):
model.step()
if not model.running:
print(f"Simulation ended at step {step}")
break
# Or run until completion
model = EcosystemModel(n_predators=10, n_prey=50)
model.run_model()
# Access agents by type
predators = model.agents_by_type[Predator]
prey = model.agents_by_type[Prey]
# Get model statistics
print(f"Final population: {len(model.agents)} agents")
print(f"Agent types: {model.agent_types}")class SocialAgent(Agent):
"""Agent with social network capabilities."""
def __init__(self, model, cooperation_probability=0.5):
super().__init__(model)
self.cooperation_prob = cooperation_probability
self.social_network = set()
self.reputation = 0.5
def add_connection(self, other_agent):
"""Add a social connection."""
self.social_network.add(other_agent)
other_agent.social_network.add(self)
def interact_with_neighbors(self):
"""Interact with agents in social network."""
for neighbor in self.social_network:
if self.random.random() < self.cooperation_prob:
# Cooperate
neighbor.reputation += 0.1
self.reputation += 0.05
else:
# Defect
neighbor.reputation -= 0.05
def update_cooperation(self):
"""Update cooperation probability based on reputation."""
self.cooperation_prob = min(1.0, max(0.0, self.reputation))
def step(self):
self.interact_with_neighbors()
self.update_cooperation()class StagedModel(Model):
"""Model using staged activation for simultaneous updates."""
def __init__(self, n_agents=100):
super().__init__()
# Create agents
for i in range(n_agents):
agent = SocialAgent(self)
self.running = True
def step(self):
"""Execute staged activation: step, then advance."""
# Stage 1: All agents observe and plan
self.agents.do("step")
# Stage 2: All agents update their state simultaneously
self.agents.do("advance")# Type aliases used in Mesa core modules
from typing import Any, Callable, Hashable, Iterable, Iterator, MutableSet, Sequence
from random import Random
import numpy as np
# Random number generation types
SeedLike = int | np.integer | Sequence[int] | np.random.SeedSequence
RNGLike = np.random.Generator | np.random.BitGenerator
# Agent and model types (forward references for documentation)
Agent = Agent
AgentSet = AgentSet[Agent]
Model = Model
GroupBy = GroupBy