1- """Forest fire with global wind """
1+ """Forest fire with global wind"""
22
3- import mesa
43import math
54
6- from mesa . space import MultiGrid
5+ import mesa
76from mesa .datacollection import DataCollector
7+ from mesa .space import MultiGrid
8+
89from .agent import Tree
910
11+
1012class ForestFire (mesa .Model ):
1113 """
1214 Wind-driven Forest Fire Model
@@ -17,8 +19,8 @@ def __init__(
1719 width = 100 ,
1820 height = 100 ,
1921 p_spread = 0.25 ,
20- wind_dir = 0.0 , # degrees
21- wind_strength = 0.8 ,
22+ wind_dir = 0.0 , # degrees
23+ wind_strength = 0.8 ,
2224 seed = None ,
2325 ignite_pos = None ,
2426 ):
@@ -37,9 +39,8 @@ def __init__(
3739 # Place trees
3840 for x in range (width ):
3941 for y in range (height ):
40- tree = Tree (self , condition = "Fine" )
41- self .grid .place_agent (tree , (x , y ))
42-
42+ tree = Tree (self , condition = "Fine" )
43+ self .grid .place_agent (tree , (x , y ))
4344
4445 # Ignite Logic
4546 if ignite_pos :
@@ -59,47 +60,52 @@ def __init__(
5960 # if random ignition, fall back to grid center
6061 self .ignite_ref = (self .width / 2 , self .height / 2 )
6162
62-
6363 # Data collector
6464 self .datacollector = DataCollector (
6565 {
6666 "Fine" : lambda m : self .count_type (m , "Fine" ),
6767 "On Fire" : lambda m : self .count_type (m , "On Fire" ),
6868 "Burned Out" : lambda m : self .count_type (m , "Burned Out" ),
6969 "HeadDist Wind" : lambda m : ForestFire .get_head_distance_wind (m ),
70- "FlankHalfwidth Cross" : lambda m : ForestFire .get_flank_halfwidth_crosswind (m ),
71- "Rate of spread at the fire head" : lambda m : ForestFire .get_head_distance_wind (m ) / max (1 , m .step_count ),
72- "Rate of spread at the fire Flank" : lambda m : ForestFire .get_flank_halfwidth_crosswind (m ) / max (1 , m .step_count ),
73-
70+ "FlankHalfwidth Cross" : lambda m : ForestFire .get_flank_halfwidth_crosswind (
71+ m
72+ ),
73+ "Rate of spread at the fire head" : lambda m : ForestFire .get_head_distance_wind (
74+ m
75+ )
76+ / max (1 , m .step_count ),
77+ "Rate of spread at the fire Flank" : lambda m : ForestFire .get_flank_halfwidth_crosswind (
78+ m
79+ )
80+ / max (1 , m .step_count ),
7481 }
7582 )
7683
7784 self .datacollector .collect (self )
7885
7986 def ignite (self , pos = None ):
80- """Ignite a specific tree or a random one."""
81- if pos is None :
82- # Random ignition
83- trees = [a for a in self .agents if a .condition == "Fine" ]
84- if trees :
85- self .random .choice (trees ).condition = "On Fire"
86- else :
87- if not self .grid .out_of_bounds (pos ):
88- cell_agents = self .grid .get_cell_list_contents ([pos ])
89- for a in cell_agents :
90- if a .condition == "Fine" :
91- a .condition = "On Fire"
92- return
93-
94-
87+ """Ignite a specific tree or a random one."""
88+ if pos is None :
89+ # Random ignition
90+ trees = [a for a in self .agents if a .condition == "Fine" ]
91+ if trees :
92+ self .random .choice (trees ).condition = "On Fire"
93+ else :
94+ if not self .grid .out_of_bounds (pos ):
95+ cell_agents = self .grid .get_cell_list_contents ([pos ])
96+ for a in cell_agents :
97+ if a .condition == "Fine" :
98+ a .condition = "On Fire"
99+ return
100+
95101 def wind_unit_vector (self ):
96102 """
97103 Convert meteorological wind direction to unit vector.
98104 0° = From North (Vector: 0, -1)
99105 """
100106 rad = math .radians (self .wind_dir )
101107 # Wind comes FROM rad, blows TOWARD opposite
102- # Grid (0,0) is bottom-left.
108+ # Grid (0,0) is bottom-left.
103109 wx = - math .sin (rad )
104110 wy = - math .cos (rad )
105111 return (wx , wy )
@@ -109,21 +115,20 @@ def wind_biased_probability(self, dx, dy):
109115 wx , wy = self .wind_unit_vector ()
110116
111117 norm = (dx ** 2 + dy ** 2 ) ** 0.5
112- if norm == 0 : return 1.0
113-
118+ if norm == 0 :
119+ return 1.0
120+
114121 dxu , dyu = dx / norm , dy / norm
115- align = dxu * wx + dyu * wy
116-
122+ align = dxu * wx + dyu * wy
123+
117124 # Simple linear bias: 1 + strength * alignment
118125 return max (0.0 , 1.0 + (self .wind_strength * align ))
119126
120-
121127 def step (self ):
122128 self .step_count += 1
123129 self .agents .shuffle_do ("step" )
124130 self .datacollector .collect (self )
125131
126-
127132 @staticmethod
128133 def count_type (model , tree_condition ):
129134 """Helper to count trees in a given condition in a fast way."""
@@ -136,8 +141,10 @@ def count_type(model, tree_condition):
136141 @staticmethod
137142 def _burnt_positions (model ):
138143 return [
139- a .pos for a in model .agents
140- if a .__class__ .__name__ == "Tree" and a .condition in ["On Fire" , "Burned Out" ]
144+ a .pos
145+ for a in model .agents
146+ if a .__class__ .__name__ == "Tree"
147+ and a .condition in ["On Fire" , "Burned Out" ]
141148 ]
142149
143150 @staticmethod
@@ -172,13 +179,3 @@ def get_flank_halfwidth_crosswind(model):
172179
173180 dev = [abs (x * px + y * py - proj_ignite_c ) for (x , y ) in pts ]
174181 return float (max (dev ))
175-
176-
177-
178-
179-
180-
181-
182-
183-
184-
0 commit comments