Extension of mesa for performance and scalability
mesa-frames
Scale Mesa beyond its limits
Classic Mesa stores each agent as a Python object, which quickly becomes a bottleneck at scale. mesa-frames reimagines agent storage using Polars DataFrames, so agents live in a columnar store rather than the Python heap.
You keep the Mesa-style Model / AgentSet structure, but updates are vectorized and memory-efficient.
Why it matters
- ⚡ 10× faster bulk updates on 10k+ agents (see Benchmarks)
- 📊 Columnar execution via Polars: SIMD ops, multi-core support
- 🔄 Declarative logic: agent rules as transformations, not Python loops
- 🚀 Roadmap: Lazy queries and GPU support for even faster models
Who is it for?
- Researchers needing to scale to tens or hundreds of thousands of agents
- Users whose agent logic can be written as vectorized, set-based operations
Why DataFrames?
DataFrames enable SIMD and columnar operations that are far more efficient than Python loops. mesa-frames currently uses Polars as its backend.
| Feature | mesa (classic) | mesa-frames | | ------- | -------------- | ----------- | | Storage | Python objects | Polars DataFrame | | Updates | Loops | Vectorized ops | | Memory overhead | High | Low | | Max agents (practical) | ~10^3 | ~10^6+ |
Benchmarks
mesa-frames consistently outperforms classic Mesa across both toy and canonical ABMs.
In the Boltzmann model, it maintains near-constant runtimes even as agent count rises, achieving up to 10× faster execution at scale.
In the more computation-intensive Sugarscape model, mesa-frames roughly halves total runtime.
We still have room to optimize performance further (see Roadmap).
Quick Start
- Install
pip install mesa-frames
Or for development:
git clone https://github.com/mesa/mesa-frames.git
cd mesa-frames
uv sync --all-extras
- Create a model
from mesa_frames import AgentSet, Model
import polars as pl
class MoneyAgents(AgentSet): def init(self, n: int, model: Model): super().init(model) self += pl.DataFrame({"wealth": pl.ones(n, eager=True)})
def give_money(self): self.select(self.wealth > 0) otheragents = self.df.sample(n=len(self.activeagents), with_replacement=True) self["active", "wealth"] -= 1 newwealth = otheragents.groupby("uniqueid").len() self[newwealth, "wealth"] += newwealth["len"]
def step(self): self.do("give_money")
class MoneyModelDF(Model): def init(self, N: int): super().init() self.sets += MoneyAgents(N, self)
def step(self): self.sets.do("step")
Roadmap
Community contributions welcome — see the full roadmap
- Transition to LazyFrames for optimization and GPU support
- Auto-vectorize existing Mesa models via decorator
- Increase possible Spaces (Network, Continuous...)
- Refine the API to align to Mesa
License
Copyright © 2025 Adam Amer, Mesa team and contributors
Licensed under the Apache License, Version 2.0.