Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions examples/ForagerBehavior/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Forager Behavior Model

A Mesa simulation demonstrating a modular behavioral framework
for agent-based models.

## Overview

Agents use a **StateMachine** to switch between behavioral states,
and attach reusable **BehaviorModules** for composable logic.

### Agent States
```
searching → (energy low) → resting → (energy restored) → searching
```

### Behavior Modules Used

| Module | Description |
|---|---|
| `EnergyDepletionBehavior` | Drains energy each step |
| `RandomMovementBehavior` | Moves agent to random neighbor |
| `ForageBehavior` | Collects resources from current cell |
| `RestBehavior` | Recovers energy when resting |

## How to Run

```bash
pip install mesa
solara run app.py
```
## Purpose

This example is a prototype for the
**GSoC 2026 Behavioral Framework for Agent Models** proposal.

Author: Khaled Saber — github.com/1khaled-ctrl
55 changes: 55 additions & 0 deletions examples/ForagerBehavior/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from mesa.visualization import SolaraViz, make_plot_component, make_space_component
from model import ForagerModel


def agent_portrayal(agent):
if agent.state == "searching":
return {"color": "#1D9E75", "size": 30}
else:
return {"color": "#4a4a8a", "size": 30}


model_params = {
"n_agents": {
"type": "SliderInt",
"value": 10,
"label": "Number of agents",
"min": 1,
"max": 30,
"step": 1,
},
"resource_density": {
"type": "SliderFloat",
"value": 0.3,
"label": "Resource density",
"min": 0.1,
"max": 0.8,
"step": 0.1,
},
"width": {
"type": "SliderInt",
"value": 20,
"min": 10,
"max": 40,
"step": 5,
},
"height": {
"type": "SliderInt",
"value": 20,
"min": 10,
"max": 40,
"step": 5,
},
}

page = SolaraViz(
ForagerModel,
components=[
make_space_component(agent_portrayal),
make_plot_component(["searching", "resting"]),
make_plot_component(["total_resources"]),
],
model_params=model_params,
name="Forager Behavior Model",
)
page
104 changes: 104 additions & 0 deletions examples/ForagerBehavior/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import mesa
from mesa.discrete_space import CellAgent, OrthogonalMooreGrid


class ForagerAgent(CellAgent):
"""
A foraging agent with state-based behavior.

States
------
searching : moves and tries to collect resources
resting : recovers energy
"""

def __init__(self, model, energy=50.0):
super().__init__(model)
self.energy = energy
self.state = "searching"

def step(self):
# Update state
if self.state == "searching" and self.energy <= 20:
self.state = "resting"
elif self.state == "resting" and self.energy >= 80:
self.state = "searching"

# Always drain energy
self.energy = max(0, self.energy - 2.0)

# State-specific behavior
if self.state == "searching":
self._move()
self._forage()
elif self.state == "resting":
self._rest()

def _move(self):
neighbors = self.cell.connections.values()
if neighbors:
self.cell = self.random.choice(list(neighbors))

def _forage(self):
pos = (self.cell.coordinate[0], self.cell.coordinate[1])
if pos in self.model.resources and self.model.resources[pos] > 0:
collected = min(15.0, self.model.resources[pos])
self.model.resources[pos] -= collected
self.energy = min(100, self.energy + collected)

def _rest(self):
self.energy = min(100, self.energy + 8.0)


class ForagerModel(mesa.Model):
"""
Foraging simulation demonstrating modular agent behavior.

Parameters
----------
n_agents : number of forager agents
width, height : grid dimensions
resource_density : fraction of cells containing resources
"""

def __init__(
self,
n_agents=10,
width=20,
height=20,
resource_density=0.3,
):
super().__init__()
self.grid = OrthogonalMooreGrid((width, height), torus=True, capacity=None)

# Place resources
self.resources = {}
for cell in self.grid.all_cells:
if self.random.random() < resource_density:
self.resources[cell.coordinate] = 20.0

# Create agents
for _ in range(n_agents):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to use create_agents method instead

agent = ForagerAgent(self)
cell = self.random.choice(list(self.grid.all_cells))
agent.cell = cell

self.datacollector = mesa.DataCollector(
agent_reporters={
"energy": "energy",
"state": "state",
},
model_reporters={
"searching": lambda m: sum(
1 for a in m.agents if a.state == "searching"
),
"resting": lambda m: sum(1 for a in m.agents if a.state == "resting"),
"total_resources": lambda m: sum(m.resources.values()),
},
)

def step(self):
self.datacollector.collect(self)
self.agents.shuffle_do("step")
for pos in self.resources:
self.resources[pos] = min(20.0, self.resources[pos] + 0.2)
Loading