Skip to content

Commit ed090dc

Browse files
committed
Add energy expenditure example demonstrating state-based agent behavior (fixes #446)
1 parent 6ccdc38 commit ed090dc

4 files changed

Lines changed: 173 additions & 0 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Energy Expenditure
2+
3+
This example explores a simple question:
4+
5+
> How do agents survive when every action costs energy?
6+
7+
Each agent has a limited internal energy that decreases over time.
8+
They must balance between moving (which helps exploration but costs energy) and resting (which recovers energy slowly).
9+
10+
## Model Behavior
11+
12+
- Every step reduces energy
13+
- Moving costs additional energy
14+
- Resting allows partial recovery
15+
- Agents are removed when their energy reaches zero
16+
17+
## What to Observe
18+
19+
When you run the model:
20+
21+
- Agents initially move actively across the grid
22+
- Over time, movement slows as energy drops
23+
- The population gradually decreases
24+
- Some agents survive longer by conserving energy
25+
26+
This creates a simple but meaningful trade-off between activity and survival.
27+
28+
## Why this example
29+
30+
This model demonstrates **state-driven behavior**, where decisions are based on an agent's internal condition rather than randomness.
31+
32+
It serves as a minimal foundation for:
33+
34+
- resource-based simulations
35+
- survival dynamics
36+
- adaptive agent behavior
37+
38+
## Run the model
39+
40+
```bash
41+
pip install -r requirements.txt
42+
solara run app.py
43+
```

examples/energy_expenditure/app.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from mesa.visualization import SolaraViz, make_space_component
2+
from model import EnergyExpenditureModel
3+
4+
5+
def agent_portrayal(agent):
6+
if agent.energy > 6:
7+
color = "#2ecc71"
8+
elif agent.energy > 3:
9+
color = "#f1c40f"
10+
else:
11+
color = "#e74c3c"
12+
13+
return {
14+
"color": color,
15+
"size": 40,
16+
}
17+
18+
19+
model = EnergyExpenditureModel()
20+
21+
page = SolaraViz(
22+
model,
23+
components=[
24+
make_space_component(agent_portrayal),
25+
],
26+
)
27+
28+
# Required for Solara to detect the app
29+
__all__ = ["page"]
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import random
2+
3+
from mesa import Agent, Model
4+
from mesa.space import MultiGrid
5+
6+
7+
class EnergyAgent(Agent):
8+
"""
9+
Agent that balances movement and survival based on internal energy.
10+
"""
11+
12+
LOW_ENERGY_THRESHOLD = 4
13+
14+
def __init__(self, model, unique_id):
15+
super().__init__(model)
16+
17+
# Store ID locally (compatible with this Mesa version)
18+
self.id = unique_id
19+
20+
# Initialize energy from model-defined range
21+
self.energy = random.randint(*self.model.initial_energy_range)
22+
23+
def step(self):
24+
"""Execute one step of agent behavior."""
25+
26+
# Base energy decay
27+
self.energy -= 1
28+
self.energy = max(self.energy, 0)
29+
30+
if self.energy == 0:
31+
self.die()
32+
return
33+
34+
# Decision based on energy level
35+
if self.energy < self.LOW_ENERGY_THRESHOLD:
36+
self.rest()
37+
else:
38+
self.move()
39+
40+
def move(self):
41+
"""Move to a neighboring cell (costs extra energy)."""
42+
43+
# Movement cost
44+
self.energy -= 1
45+
self.energy = max(self.energy, 0)
46+
47+
neighbors = self.model.grid.get_neighborhood(
48+
self.pos, moore=True, include_center=False
49+
)
50+
51+
if neighbors:
52+
new_pos = random.choice(neighbors)
53+
self.model.grid.move_agent(self, new_pos)
54+
55+
def rest(self):
56+
"""Recover energy slowly."""
57+
self.energy += 1
58+
59+
def die(self):
60+
"""Safely remove agent from grid and model."""
61+
self.model.grid.remove_agent(self)
62+
self.model.remove_agent(self)
63+
64+
65+
class EnergyExpenditureModel(Model):
66+
"""
67+
Model representing agents constrained by energy expenditure.
68+
"""
69+
70+
def __init__(self, n_agents=20, width=15, height=15):
71+
super().__init__()
72+
73+
# Define energy initialization range (removes magic numbers)
74+
self.initial_energy_range = (6, 12)
75+
76+
# Create toroidal grid
77+
self.grid = MultiGrid(width, height, torus=True)
78+
79+
# Internal agent storage (safe iteration control)
80+
self.agents_list = []
81+
82+
# Create and place agents
83+
for i in range(n_agents):
84+
agent = EnergyAgent(self, i)
85+
self.agents_list.append(agent)
86+
87+
x = self.random.randrange(width)
88+
y = self.random.randrange(height)
89+
self.grid.place_agent(agent, (x, y))
90+
91+
def step(self):
92+
"""Advance the model by one step."""
93+
for agent in list(self.agents_list):
94+
agent.step()
95+
96+
def remove_agent(self, agent):
97+
"""Remove agent from internal tracking."""
98+
if agent in self.agents_list:
99+
self.agents_list.remove(agent)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mesa[viz]
2+
matplotlib

0 commit comments

Comments
 (0)