Simulating Forest Fires with Python Cellular Automata: A Step-by-Step Guide
Learn how to model and visualize forest fire spread using a cellular automaton in Python, covering grid initialization, state transition rules, visualization with Matplotlib, statistical tracking, and parameter experiments to observe how tree density influences fire dynamics.
Simulating Forest Fires with Cellular Automata
Problem: In a forest grid (some cells may be empty), when a fire starts it spreads to neighboring trees, burning them into empty cells; empty cells remain empty.
Programming Approach
Initialize the grid using 0, 1, 2 to represent empty ground, trees, and fire.
Determine the next state of each cell based on the previous state: If the previous cell was empty, it stays empty. If the previous cell was fire, it becomes empty. If the previous cell was a tree, it becomes fire if any of its four orthogonal neighbors were fire; otherwise it remains a tree.
NumPy is used for random number generation and array operations; Matplotlib visualizes the grid.
Python Code
Import Python Libraries
<code># Import Python libraries
import numpy as np # generate arrays
import numpy.random as rand # random integers
import matplotlib.pyplot as plt # plotting
from IPython.display import display, clear_output # display
import time # time handling
</code>Set Up Simulation Space
<code>def set_board(board_size=50, f_trees_start=0.5):
'''
Create the initial grid.
Input:
board_size: length of the grid side
f_trees_start: probability that a cell contains a tree
Output: 2D numpy array with values 0, 1, 2 (empty, tree, fire)
'''
# All cells start as empty
game_board = np.zeros((board_size, board_size), dtype='int64')
# Randomly place trees according to f_trees_start
for i in range(board_size):
for j in range(board_size):
if rand.random() <= f_trees_start:
game_board[i, j] = 1
# Set the leftmost column on fire
game_board[:, 0] = 2
return game_board
</code>Generate a 50 × 50 grid:
<code>set_board()
</code>Plot the Grid
<code>def plotgrid(myarray=np.random.choice((0,1,2), size=(50,50), p=(0.55,0.4,0.05))):
# Hide axes
plt.tick_params(axis='both', which='both', bottom=False, top=False,
left=False, right=False, labelbottom=False, labelleft=False)
# Create coordinate ranges
x_range = np.linspace(0, myarray.shape[1] - 1, myarray.shape[1])
y_range = np.linspace(0, myarray.shape[0] - 1, myarray.shape[0])
x_indices, y_indices = np.meshgrid(x_range, y_range)
# Locate trees and fire
tree_x = x_indices[myarray == 1]
tree_y = y_indices[myarray == 1]
fire_x = x_indices[myarray == 2]
fire_y = y_indices[myarray == 2]
# Plot trees (green squares) and fire (red squares)
plt.plot(tree_x, myarray.shape[0] - tree_y - 1, 'gs', markersize=10)
plt.plot(fire_x, myarray.shape[0] - fire_y - 1, 'rs', markersize=10)
plt.xlim([-1, myarray.shape[1]])
plt.ylim([-1, myarray.shape[0]])
</code>Randomly generate a forest image:
<code>plotgrid()
plt.show()
</code>White cells represent empty ground, red cells fire, and green cells trees.
Update the Grid
As the fire spreads, the numbers of trees and empty cells change.
<code># Update the grid
def advance_board(game_board):
'''
Advance the simulation by one step.
Input: current grid
Output: next grid
'''
new_board = np.zeros_like(game_board)
for i in range(len(game_board)):
for j in range(len(game_board)):
place = game_board[i, j]
# Empty stays empty
if place == 0:
new_board[i, j] = 0
# Fire becomes empty
if place == 2:
new_board[i, j] = 0
# Tree may catch fire from neighbors
if place == 1:
new_board[i, j] = 1
if i > 0 and game_board[i - 1, j] == 2:
new_board[i, j] = 2
if i < game_board.shape[0] - 1 and game_board[i + 1, j] == 2:
new_board[i, j] = 2
if j > 0 and game_board[i, j - 1] == 2:
new_board[i, j] = 2
if j < game_board.shape[1] - 1 and game_board[i, j + 1] == 2:
new_board[i, j] = 2
return new_board
</code>Calculate Statistics
Compute the proportion of trees and empty cells; if these proportions stop changing, the simulation ends.
<code># Calculate statistics
def calc_stats(game_board, start_board):
'''
Compute the fractions of empty ground and trees.
Input: current grid
Output: (fraction_empty, fraction_tree)
'''
frac_empty = np.sum(game_board[game_board == 0]) / (game_board.shape[0] * game_board.shape[1])
frac_tree = np.sum(game_board[game_board == 1]) / (game_board.shape[0] * game_board.shape[1])
return frac_empty, frac_tree
</code>Main Program
Start the fire from the western edge and let it spread.
<code># Start simulation
start_board = set_board()
f_trees_start = 0.7
board_size = 50
fig = plt.figure(figsize=(10, 10))
game_board = set_board(board_size=board_size, f_trees_start=f_trees_start)
plotgrid(game_board)
on_fire = True
frac_empty_last, frac_trees_last = 0, 0
frac_fire_last = 0
while on_fire:
game_board = advance_board(game_board)
plotgrid(game_board)
time.sleep(0.1)
clear_output(wait=True)
display(fig)
fig.clear()
frac_empty, frac_trees = calc_stats(game_board, start_board)
frac_fire = 1 - frac_empty - frac_trees
print(frac_empty, frac_trees)
if frac_empty_last == frac_empty and frac_trees_last == frac_trees_last and frac_fire == frac_fire_last:
on_fire = False
frac_empty_last, frac_trees_last, frac_fire_last = frac_empty, frac_trees, frac_fire
plt.close()
</code>Initial forest image (tree density 0.7):
Evolution process:
Reducing density to 0.5 creates a sparser forest and limits fire spread:
Increasing density to 0.9 produces a denser forest and a wider fire front:
You can experiment with other parameters to observe different fire dynamics.
Model Perspective
Insights, knowledge, and enjoyment from a mathematical modeling researcher and educator. Hosted by Haihua Wang, a modeling instructor and author of "Clever Use of Chat for Mathematical Modeling", "Modeling: The Mathematics of Thinking", "Mathematical Modeling Practice: A Hands‑On Guide to Competitions", and co‑author of "Mathematical Modeling: Teaching Design and Cases".
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.