11import mesa
2+ from agents import NeedsBasedSheep , NeedsBasedWolf
23from mesa .discrete_space import OrthogonalMooreGrid
3- from agents import NeedsBasedWolf , NeedsBasedSheep
44
55
66class GrassPatch (mesa .Agent ):
@@ -9,25 +9,34 @@ def __init__(self, model, fully_grown):
99 self .grass = fully_grown
1010
1111 def step (self ):
12- if not self .grass :
13- if self .random .random () < self .model .grass_regrowth_rate :
14- self .grass = True
12+ if not self .grass and self .random .random () < self .model .grass_regrowth_rate :
13+ self .grass = True
1514
1615
1716class NeedsBasedWolfSheep (mesa .Model ):
1817 """Wolf-Sheep with needs-based behavioral drives.
19- Explores behavioral framework patterns from #2538 and #2526.
18+
19+ Explores behavioral framework patterns from discussions #2538
20+ and #2526. Unlike standard Wolf-Sheep, agents prioritise
21+ actions based on drive urgency (hunger, fear) rather than
22+ checking all conditions every tick.
23+
24+ Note: Population dynamics differ from standard Wolf-Sheep by
25+ design. Fear-suppressed eating in sheep can cause cascade
26+ extinction events — an emergent property of needs-based
27+ architecture that does not appear in the standard model.
28+ This difference is itself a finding worth exploring.
2029 """
2130
2231 def __init__ (
2332 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 ,
33+ width = 40 ,
34+ height = 40 ,
35+ initial_sheep = 200 ,
36+ initial_wolves = 15 ,
37+ sheep_reproduce = 0.12 ,
38+ wolf_reproduce = 0.04 ,
39+ grass_regrowth_rate = 0.10 ,
3140 rng = None ,
3241 ):
3342 super ().__init__ (rng = rng )
@@ -36,35 +45,27 @@ def __init__(
3645 self .grass_regrowth_rate = grass_regrowth_rate
3746
3847 self .grid = OrthogonalMooreGrid (
39- (width , height ), torus = True , capacity = None ,
40- random = self .random
48+ (width , height ), torus = True , capacity = None , random = self .random
4149 )
4250
43- # Place grass patches
4451 for cell in self .grid .all_cells :
4552 patch = GrassPatch (self , self .random .random () < 0.5 )
4653 patch .cell = cell
4754
48- # Place sheep
4955 for _ in range (initial_sheep ):
5056 cell = self .grid .all_cells .select_random_cell ()
51- sheep = NeedsBasedSheep (self , self .random .randint (1 , 6 ))
57+ sheep = NeedsBasedSheep (self , self .random .randint (4 , 8 ))
5258 sheep .cell = cell
5359
54- # Place wolves
5560 for _ in range (initial_wolves ):
5661 cell = self .grid .all_cells .select_random_cell ()
57- wolf = NeedsBasedWolf (self , self .random .randint (5 , 15 ))
62+ wolf = NeedsBasedWolf (self , self .random .randint (3 , 6 ))
5863 wolf .cell = cell
5964
6065 self .datacollector = mesa .DataCollector (
6166 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- ),
67+ "Wolves" : lambda m : len (m .agents_by_type [NeedsBasedWolf ]),
68+ "Sheep" : lambda m : len (m .agents_by_type [NeedsBasedSheep ]),
6869 }
6970 )
7071
@@ -79,8 +80,8 @@ def step(self):
7980 model .step ()
8081 data = model .datacollector .get_model_vars_dataframe ()
8182 print (
82- f"Step { i + 1 :3d} : "
83+ f"Step { i + 1 :3d} : "
8384 f"Wolves={ int (data ['Wolves' ].iloc [- 1 ]):3d} , "
8485 f"Sheep={ int (data ['Sheep' ].iloc [- 1 ]):3d} "
8586 )
86- print ("Done — model ran 100 steps successfully." )
87+ print ("Done — model ran 100 steps successfully." )
0 commit comments