Skip to content

Commit b7b9b2e

Browse files
committed
examples: Add needs-based Wolf-Sheep for behavioral framework exploration
1 parent 6ccdc38 commit b7b9b2e

4 files changed

Lines changed: 216 additions & 0 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Needs-Based Wolf-Sheep
2+
3+
An extension of the classic Wolf-Sheep predation model that
4+
explores **needs-based behavioral architecture** from Mesa
5+
discussion [#2538](https://github.com/projectmesa/mesa/discussions/2538).
6+
7+
## What's different from standard Wolf-Sheep
8+
9+
In the standard model, every agent checks all conditions every
10+
tick — energy thresholds, reproduction probability, nearby prey —
11+
regardless of whether anything changed.
12+
13+
This model restructures that logic using **explicit internal drive
14+
states** that degrade over time and determine action priority:
15+
16+
| Drive | Agent | Behaviour when urgent |
17+
|-------|-------|-----------------------|
18+
| `hunger` | Wolf, Sheep | Prioritise eating |
19+
| `fear` | Sheep | Suppress eating, flee |
20+
21+
## Key behavioral difference
22+
```python
23+
# Standard Wolf-Sheep — all checked every tick
24+
if self.energy > 20:
25+
if random() < p_reproduce:
26+
self.reproduce()
27+
28+
# Needs-based — action only fires when drive is urgent
29+
if self.hunger > 0.6: # only when actually hungry
30+
self._eat_nearest_prey()
31+
```
32+
33+
## Connection to Mesa discussions
34+
35+
- [#2538 Behavioral Framework](https://github.com/projectmesa/mesa/discussions/2538)
36+
— State system, drive-based decision making
37+
- [#2526 Tasks](https://github.com/projectmesa/mesa/discussions/2526)
38+
— Desire-based modelling, internal states
39+
40+
## Running the model
41+
```bash
42+
pip install mesa
43+
python model.py
44+
```
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import mesa
2+
from mesa.discrete_space import CellAgent
3+
4+
5+
class NeedsBasedAnimal(CellAgent):
6+
"""Base class with explicit internal drive states.
7+
Connects to Mesa discussion #2538 State Management component.
8+
"""
9+
10+
def __init__(self, model, energy):
11+
super().__init__(model)
12+
self.energy = energy
13+
self.hunger = 0.0
14+
self.fear = 0.0
15+
16+
def update_drives(self):
17+
self.hunger = min(1.0, self.hunger + 0.08)
18+
self.fear = max(0.0, self.fear - 0.1)
19+
20+
def move(self):
21+
self.cell = self.random.choice(
22+
list(self.cell.connections.values())
23+
)
24+
25+
26+
class NeedsBasedWolf(NeedsBasedAnimal):
27+
28+
def step(self):
29+
self.move()
30+
self.energy -= 1
31+
self.update_drives()
32+
33+
if self.energy <= 0:
34+
self.remove()
35+
return
36+
37+
if self.hunger > 0.6:
38+
sheep = [
39+
a for a in self.cell.agents
40+
if isinstance(a, NeedsBasedSheep)
41+
]
42+
if sheep:
43+
prey = self.random.choice(sheep)
44+
self.energy += 4
45+
self.hunger = 0.0
46+
prey.remove()
47+
return
48+
49+
if (self.energy > 20 and self.fear < 0.3
50+
and self.hunger < 0.4):
51+
if self.random.random() < self.model.wolf_reproduce:
52+
self.energy //= 2
53+
NeedsBasedWolf(self.model, self.energy)
54+
55+
56+
class NeedsBasedSheep(NeedsBasedAnimal):
57+
58+
def step(self):
59+
self.move()
60+
self.energy -= 1
61+
self.update_drives()
62+
63+
wolves_nearby = sum(
64+
1 for a in self.cell.agents
65+
if isinstance(a, NeedsBasedWolf)
66+
)
67+
if wolves_nearby > 0:
68+
self.fear = min(1.0, self.fear + 0.5)
69+
70+
if self.energy <= 0:
71+
self.remove()
72+
return
73+
74+
if self.hunger > 0.5 and self.fear < 0.7:
75+
if self.cell.grass:
76+
self.energy += 4
77+
self.hunger = 0.0
78+
self.cell.grass = False
79+
return
80+
81+
if (self.energy > 6 and self.fear < 0.2
82+
and self.hunger < 0.3):
83+
if self.random.random() < self.model.sheep_reproduce:
84+
self.energy //= 2
85+
NeedsBasedSheep(self.model, self.energy)
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import mesa
2+
from mesa.discrete_space import OrthogonalMooreGrid
3+
from agents import NeedsBasedWolf, NeedsBasedSheep
4+
5+
6+
class GrassPatch(mesa.Agent):
7+
def __init__(self, model, fully_grown):
8+
super().__init__(model)
9+
self.grass = fully_grown
10+
11+
def step(self):
12+
if not self.grass:
13+
if self.random.random() < self.model.grass_regrowth_rate:
14+
self.grass = True
15+
16+
17+
class NeedsBasedWolfSheep(mesa.Model):
18+
"""Wolf-Sheep with needs-based behavioral drives.
19+
Explores behavioral framework patterns from #2538 and #2526.
20+
"""
21+
22+
def __init__(
23+
self,
24+
width=20,
25+
height=20,
26+
initial_sheep=100,
27+
initial_wolves=25,
28+
sheep_reproduce=0.04,
29+
wolf_reproduce=0.05,
30+
grass_regrowth_rate=0.03,
31+
rng=None,
32+
):
33+
super().__init__(rng=rng)
34+
self.sheep_reproduce = sheep_reproduce
35+
self.wolf_reproduce = wolf_reproduce
36+
self.grass_regrowth_rate = grass_regrowth_rate
37+
38+
self.grid = OrthogonalMooreGrid(
39+
(width, height), torus=True, capacity=None,
40+
random=self.random
41+
)
42+
43+
# Place grass patches
44+
for cell in self.grid.all_cells:
45+
patch = GrassPatch(self, self.random.random() < 0.5)
46+
patch.cell = cell
47+
48+
# Place sheep
49+
for _ in range(initial_sheep):
50+
cell = self.grid.all_cells.select_random_cell()
51+
sheep = NeedsBasedSheep(self, self.random.randint(1, 6))
52+
sheep.cell = cell
53+
54+
# Place wolves
55+
for _ in range(initial_wolves):
56+
cell = self.grid.all_cells.select_random_cell()
57+
wolf = NeedsBasedWolf(self, self.random.randint(5, 15))
58+
wolf.cell = cell
59+
60+
self.datacollector = mesa.DataCollector(
61+
model_reporters={
62+
"Wolves": lambda m: len(
63+
m.agents_by_type[NeedsBasedWolf]
64+
),
65+
"Sheep": lambda m: len(
66+
m.agents_by_type[NeedsBasedSheep]
67+
),
68+
}
69+
)
70+
71+
def step(self):
72+
self.agents.shuffle_do("step")
73+
self.datacollector.collect(self)
74+
75+
76+
if __name__ == "__main__":
77+
model = NeedsBasedWolfSheep()
78+
for i in range(100):
79+
model.step()
80+
data = model.datacollector.get_model_vars_dataframe()
81+
print(
82+
f"Step {i+1:3d}: "
83+
f"Wolves={int(data['Wolves'].iloc[-1]):3d}, "
84+
f"Sheep={int(data['Sheep'].iloc[-1]):3d}"
85+
)
86+
print("Done — model ran 100 steps successfully.")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mesa

0 commit comments

Comments
 (0)