Skip to content

Commit 6c4b411

Browse files
[pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
1 parent 63d89e5 commit 6c4b411

File tree

4 files changed

+77
-81
lines changed

4 files changed

+77
-81
lines changed

examples/wind_driven_fire/README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,29 @@ Fire spreads probabilistically between neighboring trees, with ignition probabil
1515

1616
### Fire Spread Rule
1717

18-
At each time step, trees that are *On Fire* attempt to ignite their neighbors (Moore neighborhood).
18+
At each time step, trees that are *On Fire* attempt to ignite their neighbors (Moore neighborhood).
1919
The ignition probability is modified by wind alignment:
2020

2121
$$
2222
p = p_{\text{spread}} \cdot \left( 1 + s \cdot \cos(\theta) \right)
2323
$$
2424
where:
25-
- $ p_{\text{spread}} $ is the baseline spread probability
26-
- $ s $ is wind strength
27-
- $ \theta $ is the angle between the wind direction and the neighbor direction
25+
- $ p_{\text{spread}} $ is the baseline spread probability
26+
- $ s $ is wind strength
27+
- $ \theta $ is the angle between the wind direction and the neighbor direction
2828
After spreading fire, burning trees transition to *Burned Out*.
2929

3030

3131
### Wind Representation
3232

3333
Wind is represented as a **global vector field** defined by:
3434

35-
- **Wind direction (degrees)** using the meteorological convention (wind coming *from*)
36-
- **Wind strength**, controlling the magnitude of directional bias
35+
- **Wind direction (degrees)** using the meteorological convention (wind coming *from*)
36+
- **Wind strength**, controlling the magnitude of directional bias
3737

3838
### Fire-Head–Based Rate of Spread
3939

40-
- **Head Distance (Downwind)**: The **fire head** is defined as the furthest burned cell projected onto the wind axis.
40+
- **Head Distance (Downwind)**: The **fire head** is defined as the furthest burned cell projected onto the wind axis.
4141
Fire-head distance is measured relative to the ignition reference point:
4242

4343
$$

examples/wind_driven_fire/app.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
from mesa.visualization import SolaraViz, make_space_component, make_plot_component
1+
from mesa.visualization import SolaraViz, make_plot_component, make_space_component
22
from mesa.visualization.user_param import Slider
3-
import matplotlib.cm as cm
4-
from matplotlib.colors import to_hex
53
from wind_driven_fire.agent import Tree
64
from wind_driven_fire.model import ForestFire
75

@@ -10,17 +8,13 @@
108
COLORS = {"Fine": "#00AA00", "On Fire": "#880000", "Burned Out": "#000000"}
119

1210
cx, cy = WIDTH // 2, HEIGHT // 2
13-
center_9_points = [
14-
(x, y)
15-
for x in range(cx - 1, cx + 2)
16-
for y in range(cy - 1, cy + 2)
17-
]
11+
center_9_points = [(x, y) for x in range(cx - 1, cx + 2) for y in range(cy - 1, cy + 2)]
1812

1913

2014
def forest_fire_portrayal(agent):
2115
if agent is None:
2216
return
23-
17+
2418
class_name = type(agent).__name__
2519

2620
# Tree
@@ -30,26 +24,34 @@ def forest_fire_portrayal(agent):
3024
"size": 20,
3125
"marker": "s",
3226
}
33-
27+
28+
3429
ros_plot = make_plot_component(
35-
{"Rate of spread at the fire head": "blue", "Rate of spread at the fire Flank": "orange"}
30+
{
31+
"Rate of spread at the fire head": "blue",
32+
"Rate of spread at the fire Flank": "orange",
33+
}
3634
)
3735

3836

39-
model = ForestFire(width=100, height=100, p_spread=0.25, wind_dir=0.0, wind_strength=0.8,ignite_pos=center_9_points)
37+
model = ForestFire(
38+
width=100,
39+
height=100,
40+
p_spread=0.25,
41+
wind_dir=0.0,
42+
wind_strength=0.8,
43+
ignite_pos=center_9_points,
44+
)
4045
model_params = {
4146
"height": HEIGHT,
4247
"width": WIDTH,
4348
"p_spread": Slider("Spread probability", 0.25, 0.0, 1.0, 0.05),
4449
"wind_dir": Slider("Wind direction (deg)", 0.0, 0.0, 360.0, 5.0),
4550
"wind_strength": Slider("Wind strength", 0.8, 0.0, 1.0, 0.1),
46-
"ignite_pos": center_9_points #central ignite
51+
"ignite_pos": center_9_points, # central ignite
4752
}
4853

49-
space_component = make_space_component(
50-
forest_fire_portrayal,
51-
backend="matplotlib"
52-
)
54+
space_component = make_space_component(forest_fire_portrayal, backend="matplotlib")
5355

5456
page = SolaraViz(
5557
model,
@@ -61,10 +63,10 @@ def forest_fire_portrayal(agent):
6163
name="Wind-Driven Forest Fire ",
6264
description="""
6365
**Anisotropic Fire Spread Demonstration**
64-
66+
6567
This example highlights how wind introduces **anisotropy** into the system.
66-
68+
6769
* **Visual Proof**: The blue arrow indicates wind direction. Note how the fire scar (black agents) elongates along the arrow's direction.
6870
* **Statistical Proof**: Observe the chart below. "Span Y" vs "Span X" shows the divergence in spread width vs height, confirming the fire is elliptical, not circular.
69-
"""
70-
)
71+
""",
72+
)

examples/wind_driven_fire/wind_driven_fire/agent.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,10 @@ def step(self):
1818

1919
# Spread fire (8-neighborhood)
2020
neighbors = self.model.grid.get_neighbors(
21-
self.pos,
22-
moore=True,
23-
include_center=False)
24-
25-
for n in neighbors:
21+
self.pos, moore=True, include_center=False
22+
)
2623

24+
for n in neighbors:
2725
if n.condition != "Fine":
2826
continue
2927
# Calculate wind bias
@@ -38,4 +36,3 @@ def step(self):
3836

3937
# After spreading fire, this tree burns out
4038
self.condition = "Burned Out"
41-

examples/wind_driven_fire/wind_driven_fire/model.py

Lines changed: 44 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
"""Forest fire with global wind """
1+
"""Forest fire with global wind"""
22

3-
import mesa
43
import math
54

6-
from mesa.space import MultiGrid
5+
import mesa
76
from mesa.datacollection import DataCollector
7+
from mesa.space import MultiGrid
8+
89
from .agent import Tree
910

11+
1012
class 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

Comments
 (0)