Note

You can download this demonstration as a Jupyter Notebook here

Forest fire¶

This notebook presents an agent-based model that simulates a forest fire. It demonstrates how to use the agentpy package to work with a spatial grid and create animations, and perform a parameter sweep.

[1]:

# Model design
import agentpy as ap
import numpy as np

# Visualization
import matplotlib.pyplot as plt
import seaborn as sns
import IPython


The model ist based on the NetLogo FireSimple model by Uri Wilensky and William Rand, who describe it as follows:

“This model simulates the spread of a fire through a forest. It shows that the fire’s chance of reaching the right edge of the forest depends critically on the density of trees. This is an example of a common feature of complex systems, the presence of a non-linear threshold or critical parameter. […]

The fire starts on the left edge of the forest, and spreads to neighboring trees. The fire spreads in four directions: north, east, south, and west.

The model assumes there is no wind. So, the fire must have trees along its path in order to advance. That is, the fire cannot skip over an unwooded area (patch), so such a patch blocks the fire’s motion in that direction.”

Model definition¶

[2]:

class ForestModel(ap.Model):

def setup(self):

# Create grid (forest)

# Create agents (trees)
n_trees = int(self.p.density * (self.p.size**2))

# Initiate a dynamic variable for all trees
# Condition 0: Alive, 1: Burning, 2: Burned
self.agents.condition = 0

# Start a fire from the left side of the grid
unfortunate_trees = forest.get_agents([(0, self.p.size), (0, 0)])
unfortunate_trees.condition = 1

def step(self):

# Select burning trees
burning_trees = self.agents.select(self.agents.condition == 1)

for agent in burning_trees:
for neighbor in agent.neighbors():
if neighbor.condition == 0:
neighbor.condition = 1 # Neighbor starts burning
agent.condition = 2 # Tree burns out

# Stop simulation if no fire is left
if len(burning_trees) == 0: self.stop()

def end(self):

# Document a measure at the end of the simulation
burned_trees = len(self.agents.select(self.agents.condition == 2))
self.measure('Percentage of burned trees',
burned_trees / len(self.agents))


Single-run animation¶

[3]:

# Define parameters

parameters = {
'density': 0.6, # Percentage of grid covered by trees
'size': 50 # Height and length of the grid
}

[4]:

# Create single-run animation with custom colors

def animation_plot(model, ax):
attr_grid = model.env.attribute('condition', empty=3)
color_dict = {0:'#7FC97F', 1:'#d62c2c', 2:'#e5e5e5', 3:'#d5e5d5'}
ap.gridplot(attr_grid, ax=ax, color_dict=color_dict, convert=True)
ax.set_title(f"Simulation of a forest fire\n"
f"Time-step: {model.t}, Trees left: "
f"{len(model.agents.select(model.agents.condition == 0))}")

fig, ax = plt.subplots()
model = ForestModel(parameters)
animation = ap.animate(model, fig, ax, animation_plot)
IPython.display.HTML(animation.to_jshtml())

[4]:


Parameter sweep¶

[5]:

# Prepare parameter sample
# Arranges 30 values for density from 0.1 to 1

parameter_ranges = {
'density': (0.2,0.6),
'size': 100
}

sample = ap.sample(parameter_ranges, n=30)

[6]:

# Perform experiment
# Repeats simulation 30 times for each value of density

exp = ap.Experiment(ForestModel, sample, iterations=30)
results = exp.run()

Scheduled runs: 900
Completed: 900, estimated time remaining: 0:00:00
Experiment finished
Run time: 0:04:52.087680

[7]:

# To save and load data

# results.save()
# results = ap.load('ForestModel')

[8]:

# Plot sensitivity
# Every point shows average over 50 runs

data = results.arrange_measures() # Create plotting data

sns.set()
ax = sns.lineplot(data=data, x='density', y='Percentage of burned trees')