Forest fires are a fact of life on a planet covered in trees.
Some are minor occurrences, burning over a few acres before being snuffed out without event, while others take hold of
hundreds of square miles, rage for months and destroy tremendous swaths of forest and property.
Knowing which course a wildfire will take is difficult and involves a number of factors that are hard to model, but there are fundamental questions we might expect to be insensitive to the details such as
How long will a typical fire burn?
What fraction of a typical forest will be destroyed?
Are there any structural features we can exploit to reduce the severity of forest fires?
Here, we're going to build a simple model that we can interrogate in a controlled way.
First of all, we're going to model the forest as an
L
×
L
lattice, at each point of which a tree can exist or not.
Next, we're going to assume that trees are placed at lattice points with probability
ρ
,
so that the expected number of trees in the forest is
⟨
Trees
⟩
=
ρ
L
2
.
Finally, any given tree burns for one timestep, and if a tree at point
(
i
,
j
)
is on fire at time
t
,
all nearest-neighbor trees will be on fire at time
t
+
1
.
We start the fire by randomly lighting one tree at time zero.
As we increase
ρ
,
the density of trees in the forest increases, as does the likelihood that trees form clusters.
Therefore, we might expect that the extent and duration of the average forest fire varies as some function of
ρ
.
When
L
=
2
5
,
find
ρ
max
,
the tree density at which the expected duration of the forest fire is maximal.
Note:
The code environment below has a runtime limit of
1
0
s
,
so be judicious in how you measure the forest. There are three functions in the imported module available for your use:
add_lists()
which takes two lists of length two and returns their vector sum,
dedupe_list()
which takes a list of lists and returns its unique elements, and
print_forest()
which takes a forest lattice and returns a pretty print representation of the state of the forest.
Diagram: At time
t
=
0
,
the tree at
(
1
,
1
)
is lit on fire. At time
t
=
1
the first tree is burned out and the fire has spread to the trees at
(
1
,
0
)
and
(
0
,
1
)
.
import random
import numpy as np
import matplotlib.pyplot as plt
from brilliant import forest_fire_module as fire
def simulate(p, L):
# Initializes the lattice with a random placement of trees and a record of their status (burned or not burned)
lattice = [[(["Tree", "NotBurned"] if random.random() < p else ["Empty", None])
for i in range(L)]
for j in range(L)]
# Possible moves for the fire to make.
moves = [[1,0], [0,1], [-1,0], [0,-1]]
# Randomly select a tree from the forest to set on fire.
trees = [np.array([i, j])
for i in range(L)
for j in range(L)
if lattice[i][j][0] is "Tree"]
initial = random.choice(trees)
# Set its status to burned in the lattice.
lattice[initial[0]][initial[1]][1] = "Burned"
# Set counters for the number of burned trees and the total
# duration of the forest fire.
(burn_duration, trees_burned) = (1, 1)
# Initialize a list of all trees currently on fire.
burning = [initial]
print("The fire starts at", initial, "\n")
fire.print_forest(lattice)
# Fill this in with your code that propagates the fire across the forest.
# You may find the function print_forest (used above) useful in checking your dynamics.
# You may find the functions add_lists and dedupe_list useful as well.
return [burn_duration, trees_burned, len(trees)]
simulate(0.4, 25)
# # Once your simulation function is working, uncomment this code and use it to measure duration
# # as a function of tree density.
# probabilities = np.arange(0.1, 1., 0.1)
# mean_burned = []
# mean_durations = []
#
# for p in probabilities:
# tmp_burn_durations = []
# tmp_fractions_burned = []
# for measurement in range(100):
# [burn_duration, trees_burned, trees_total] = simulate(p, 25)
# tmp_burn_durations.append(burn_duration)
# tmp_fractions_burned.append(trees_burned / trees_total)
# mean_burned.append(np.mean(tmp_fractions_burned))
# mean_durations.append(np.mean(tmp_burn_durations))
# plt.plot(probabilities, mean_durations)
# plt.savefig('myplot.png')
This section requires Javascript.
You are seeing this because something didn't load right. We suggest you, (a) try
refreshing the page, (b) enabling javascript if it is disabled on your browser and,
finally, (c)
loading the
non-javascript version of this page
. We're sorry about the hassle.
First thing first: sorry for posting a solution with Matlab code instead of python, but I'm more proficient with the former and the code is fully functional with base Matlab Home Edition without any additional toolbox.
The solution is around 0.7, so it falls in the range [0.61, 0.75)
Considerations about the solution can be found as comments embedded in the source code
clearvars
close all
clc
rVec = 0.1:0.1:1; % vector of tree densities
rLen = length(rVec);
tVec = 1:1000; % vector of esperiments per tree density
% NOTE: I found that 100 experiments (as suggested by the code snippet
% in the problem text) are not always enough to account for the experiments
% variance. In facts in my density vector the value that gives the longest
% mean duration is 0.7, because it seems to be the best compromise between
% burning a lot of trees but not doing that in a single burst. Well, with
% such a value you can have durations as low as 2 epochs (the first tree
% is isolated) and as long as 80 epochs with a standard deviation
% of about 11.4 epochs, as shown by the code statistics in the end.
% When the trees density is 0.6 the variance is even greater.
tLen = length(tVec);
D = zeros(rLen, tLen); % Matrix of durations
N = 25; % Lattice dimension
hr = waitbar(0, 'percentage');
for rIdx = 1:rLen
r = rVec(rIdx); % select a density
ht = waitbar(0, 'measurements');
for tIdx = 1:tLen
D(rIdx, tIdx) = burnTrees(r, N, false); % perform an experiment
waitbar(tIdx/tLen, ht)
end
close(ht)
waitbar(rIdx/rLen, hr)
end
close(hr)
dMean = mean(D, 2);
dMin = min(D, [], 2);
dMax = max(D, [], 2);
dStd = std(D, [], 2);
figure
errorbar(rVec', dMean, dStd, 'b')
hold on
plot(rVec', dMin, 'r', rVec', dMax, 'g')
legend('mean\pm std', 'min', 'max')
xlabel('trees density')
ylabel(['duration statistics for ', num2str(tLen), ' experiments'])
fprintf('%8s%16s%16s%16s%16s\n', 'density', 'mean duration', 'duration std', 'min durartion', 'max duration')
for rIdx = 1:rLen
fprintf('%8.1f%16.2f%16.2f%16d%16d\n', rVec(rIdx), dMean(rIdx), dStd(rIdx), dMin(rIdx), dMax(rIdx))
end
[~, idxMax] = max(dMean);
rMax = rVec(idxMax);
d = burnTrees(rMax, N, true);
%------ simulating function ------%
function D = burnTrees(r, N, pFlag)
T = rand(N, N) < r; % initialize trees lattice
B = false(N, N); % initialize burning trees lattice
% NOTE: burning right now, not burnt
tIdx = find(T);
bIdx = ceil(length(tIdx)*rand(1, 1)); % select a random tree to burn
B(tIdx(bIdx)) = true; % set it on fire on first epoch
D = 0; % initialize fire duration
Tprev = false(N, N); % initialize a previous state of the trees lattice
if pFlag % plot flag
I = zeros(N, N); % initialize a lattice to represent trees either in
% burning state or not
figure
end
while any(any(xor(Tprev, T))) % simulate until there's no change between actual
% trees state and previous one
D = D+1; % count epochs
% NOTE: not sure about duration offset, maybe counting 1 epoch more or
% less but not really important for the sake of comparison
if pFlag % if plot flag
I(T) = 1; % 1 for trees not burning
I(B) = 2; % 2 for trees burning
I(~(T | B)) = 0; % 0 for nothing (either trees already burnt or no trees at all)
imagesc(I)
title(num2str(D))
drawnow
pause(0.1)
end
Tprev = T; % update previous trees state
T = T & ~B; % delete trees that were burning in the previous epoch (become burnt trees)
% update burning trees state: compare the trees lattice with shifted
% versions (respectively down, up, right, left) of the burning trees
% during the previous epoch
B = ([false(1, N); B(1:N-1, :)] | [B(2:N, :); false(1, N)] | ...
[false(N, 1), B(:, 1:N-1)] | [B(:, 2:N), false(N, 1)]) & T;
end
end
Funny, I was going to come to the discussion and say the same... "I'm ashamed I don't know python, I had to do it in matlab, forgive-me and"... the first solution of the discussion is in matlab. I'm more relieved now. I'll not put the code for now, I didn't went so far as to produce the std, but I want to do that when I'll have more time. But I took more trials (10000) and tried a greater resolution (0.01), and the best result was around 0.69 . Maximum average duration was 41.3 . The question from Siverman is interesting because the longest fire is also the one that offers more time to fight it. I guess this implies the smaller number of fires each time, and so, less human resources needed to fight it at the same time. But thinking on this terms, we'll not have another interesting densities to be found?
First thing first: sorry for posting a solution with Matlab code instead of python, but I'm more proficient with the former and the code is fully functional with base Matlab Home Edition without any additional toolbox.
The solution is around 0.7, so it falls in the range [0.61, 0.75)
Considerations about the solution can be found as comments embedded in the source code