Session 3: space, rasters and percolation#
Zoo model: Forest fire
Goals#
Put a model on a grid, and number its cells with a
CellIndexso that a whole-landscape question (“which trees touch a fire?”) is one sparse product.Read a per-cell state as a raster (
index.to_layer) and plot it.Meet a phase transition: the percolation threshold.
Before the session: the adding space tutorial and Vectorised step phases, section 2.
Exercises#
3.1 Run the forest fire at densities 0.5, 0.6 and 0.7 on a 100 x 100 raster. For each, print the burnt share and whether the fire reached the east edge.
Solution
from modelly.zoo.forest_fire.model import ForestFire, ForestScenario
for density in (0.5, 0.6, 0.7):
model = ForestFire(ForestScenario(rng=1, density=density))
model.run()
print(density, round(model.burnt_share(), 2), model.spans())
At 0.5 the fire dies near the west edge; at 0.7 it crosses. 0.6 is close to the threshold
p_c = 0.5927, where the outcome depends on the seed.
3.2 Estimate the probability that the fire spans the raster at density 0.59, from twenty seeds, on rasters of side 30 and 120. What happens to the transition as the raster grows?
Solution
import numpy as np
for side in (30, 120):
spans = []
for seed in range(20):
model = ForestFire(
ForestScenario(rng=seed, width=side, height=side, density=0.59)
)
model.run()
spans.append(model.spans())
print(side, np.mean(spans))
Near p_c the spanning probability is neither 0 nor 1; the window of densities where it
is uncertain narrows as the raster grows (as L^(-3/4) for two-dimensional percolation),
so on an infinite landscape the transition is sharp.
3.3 In ForestFire.step, which line finds the trees next to a fire? Explain why it is one
sparse matrix-vector product and not a loop over trees.
Solution
exposed = self.index.neighbor_sum(burning.astype(np.int32)) > 0. CellIndex builds the
adjacency matrix of the grid once; multiplying it by the vector “is burning” gives, for every
cell, the number of burning neighbours.
3.4 Make a landscape with a firebreak: no trees in a vertical strip in the middle. Pass it
as cover and check that the fire stops at the strip even at density 0.8.
Solution
side = 100
cover = np.full((side, side), 0.8) # indexed [x, y]
cover[48:52, :] = 0.0
model = ForestFire(ForestScenario(rng=1, density=0.8, cover=cover))
model.run()
print(model.spans()) # False
A land-cover raster read with modelly.space.geo.RasterLayer has the same [x, y] layout in
its values.