Skip to content

Commit 6e94dad

Browse files
committed
Add energy expenditure example demonstrating state-based agent behavior (fixes #446)
Refactor EnergyAgent and EnergyModel classes [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Refactor energy portrayal and model initialization Updated agent portrayal logic to use LOW_ENERGY_THRESHOLD for energy levels. Refactored model initialization for easier parameter tweaking. Fix Ruff lint: use lowercase parameter name for agent count Update README and align parameter naming in app/model
1 parent 6ccdc38 commit 6e94dad

4 files changed

Lines changed: 150 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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from mesa.visualization import SolaraViz, make_space_component
2+
from model import EnergyModel
3+
4+
5+
def agent_portrayal(agent):
6+
"""Visual representation of agent based on energy level."""
7+
8+
if agent.energy > agent.LOW_ENERGY_THRESHOLD + 2:
9+
color = "#2ecc71" # high energy (green)
10+
elif agent.energy > agent.LOW_ENERGY_THRESHOLD:
11+
color = "#f1c40f" # medium energy (yellow)
12+
else:
13+
color = "#e74c3c" # low energy (red)
14+
15+
return {
16+
"color": color,
17+
"size": 40,
18+
}
19+
20+
21+
# Allow easy parameter tweaking
22+
model = EnergyModel(
23+
n=20,
24+
width=10,
25+
height=10,
26+
)
27+
28+
page = SolaraViz(
29+
model,
30+
components=[
31+
make_space_component(agent_portrayal),
32+
],
33+
)
34+
35+
__all__ = ["page"]
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from mesa import Agent, Model
2+
from mesa.space import MultiGrid
3+
4+
5+
class EnergyAgent(Agent):
6+
# --- Tunable Parameters ---
7+
LOW_ENERGY_THRESHOLD = 4
8+
MOVE_DECAY = 1.0
9+
REST_DECAY = 0.4
10+
11+
def __init__(self, model, energy=10):
12+
super().__init__(model)
13+
self.energy = energy
14+
self.alive = True
15+
16+
def step(self):
17+
if not self.alive:
18+
return
19+
20+
is_resting = self.energy < self.LOW_ENERGY_THRESHOLD
21+
22+
if is_resting:
23+
self.rest()
24+
decay = self.REST_DECAY
25+
else:
26+
self.move()
27+
decay = self.MOVE_DECAY
28+
29+
self.energy = max(self.energy - decay, 0)
30+
31+
if self.energy == 0:
32+
self._die()
33+
34+
def move(self):
35+
neighbors = self.model.grid.get_neighborhood(
36+
self.pos, moore=True, include_center=False
37+
)
38+
if neighbors:
39+
new_pos = self.random.choice(neighbors)
40+
self.model.grid.move_agent(self, new_pos)
41+
42+
def rest(self):
43+
pass
44+
45+
def _die(self):
46+
self.alive = False
47+
self.model.grid.remove_agent(self)
48+
self.remove() # deregisters from model AgentSet
49+
50+
51+
class EnergyModel(Model):
52+
def __init__(self, n=10, width=10, height=10):
53+
super().__init__()
54+
self.num_agents = n
55+
self.grid = MultiGrid(width, height, torus=True)
56+
57+
for _ in range(self.num_agents):
58+
agent = EnergyAgent(self)
59+
x = self.random.randrange(self.grid.width)
60+
y = self.random.randrange(self.grid.height)
61+
self.grid.place_agent(agent, (x, y))
62+
63+
self.running = True
64+
65+
def step(self):
66+
# RandomActivation replacement in Mesa 3.x
67+
self.agents.shuffle_do("step")
68+
69+
if len(self.agents) == 0:
70+
self.running = False
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)