The Fire Model

2023.05.09 00:00

Fun Projects

Who should read it

Anyone interested in programming or to learn about the spread of wildfires.

Simple but instructive

The Fire Model is an agent-based model used to simulate wildfires and how they spread. There are two reasons why I like this model:

  • It is easy to understand, and thus a perfect entry point to start programming.
  • Despite its simplicity, the results are extremely interesting.

How it works

The forest is simulated using a grid of trees and non-flammable objects (e.g. rocks). On the left of the grid there is a fire line and the fire spreads from left to right. However, as rocks can’t burn they limit the spread of the fire. Now there is one model assumption that fire spreads from tree to tree and a tree catches fire when it is in direct contact with the fire line. The fire line moves from left to right as long as there are trees touching the fire line. For simplicity, only direct neighbors are considered, and fire does not spread diagonally.

Here is an example of a simulated forest with a fire line on the left (no trees burned yet).

Now there is one parameter we can vary and that is the tree density of the forest. For example, we could set the tree density to 100 and once fire spreads the whole forest would burn off. On the other extreme, a tree density of zero would imply no possibility for the fire to spread as there are no flammable materials. In the example above tree density is set to 57%. So we choose a tree density and then the forest is populated randomly with trees. Now one potential research question is:

Given a certain tree density, how many trees will burn off?

If we assume that there is a city on the right side of the forest, we could ask a more practical question,:

Will the fire reach the city at a certain tree density?

Take a moment to guess how far the fire will spread in the simulated forest above.

Now run the animation (does not work on iPhone).

Now we simulate a new forest but slighlty increase the tree density from 57% to 62%. Again guess what will happen before running the animation.

This is an astonishing result. We have only slightly increased the tree density, but now the city would have been reached and more than 90% of all trees have burned down. Such abrupt changes from one state to another are called phase transitions and are often observed in complex systems. This should also be our main takeaway:

In many fields we learn and develop linear models, but this often does not even come close to reality. This is a reminder to keep in mind, particularly during times of change.

Exercises

  1. Before looking at the code below, use the following pseudo code to implement the fire model in your preferred programming language.
    • randomly populate a grid with trees and rocks
    • the fire line is assumed to be on the left of the grid, set all trees that touch the line on fire
    • each tree that borders an already burning tree (north, east, west or north) also catches fire
    • continue as long as there are no new trees catching fire
  2. Try to extend the model by including parameters for wind direction and force. How could this affect the spread of wildfires?

Appendix: Code

Below you find the code to create the animations above. It is not intented to be the most efficient or the most practical one but I think this way it is easier to understand.

All steps were saved in a separate .RDS-file as otherwise we would run into the problem of growing vectors.

In the animations above, I set matrix_size to 200 just to make it more visually appealing.

library( tidyr )
library( dplyr )
library( ggplot2 )

# set file path where you want to store the data
  FILE_PATH <- ""

# set density and initialize matrix/forest
  density     <- 62
  matrix_size <- 100

  # create forest grid
  set.seed( 20230508 )
  mat <- matrix( sample( c( 0, 1 ),
                         replace = TRUE,
                         size = matrix_size^2,
                         prob = c( 1-density/100,
                                   density/100 ) ),
                 ncol = matrix_size )

# convert to long format as it is easier to use when plotting
  df <- as.data.frame( mat ) %>%
    mutate( row_idx = 1:matrix_size ) %>%
    pivot_longer( -row_idx, names_to = "col_idx" ) %>%
    mutate( col_idx = as.numeric( gsub( "V", "", col_idx ) ),
            time = 0,
            row_col = paste( row_idx, col_idx ) )
  
  # save initial forest
  saveRDS( df, file = paste0( FILE_PATH, "burn_step_0.Rds" ) )

  # start fire and save first plot
  df <- df %>%
    mutate( value = ifelse( col_idx == 1 & value == 1, 0.8, value ),
            time = 1 )
  
  saveRDS( df, file = paste0( FILE_PATH, "burn_step_1.Rds" ) )

  # observe how the fire spreads
  on_fire <- df
  TIME <- 1
  while( nrow( on_fire ) > 0 ){
  
    TIME <- TIME + 1
    print( TIME )
  
    # find burning trees
    on_fire <- df[ df$value == 0.8, ]
  
    # find bordering trees of the trees that are currently burning
    # move left/right
    right_col <- on_fire$col_idx + 1
    left_col <- on_fire$col_idx - 1
    right_row <- left_row <- on_fire$row_idx
    # move up/down
    up_col <- down_col <- on_fire$col_idx
    up_row <- on_fire$row_idx + 1
    down_row <- on_fire$row_idx - 1
  
    # define trees catching fire
    on_fire <- data.frame( row_idx = c( right_row, left_row, up_row, down_row ),
                           col_idx = c( right_col, left_col, up_col, down_col ) )
  
    on_fire <- on_fire %>%
      filter( row_idx > 0, col_idx > 0 ) %>%
      unique() %>%
      mutate( row_col = paste( row_idx, col_idx ) )
  
    # burn down already burning trees
    df <- df %>%
      mutate( value = ifelse( value != 1 & value > 0.3, value - 0.2, value ),
              time = TIME )
  
    # assign newly burning trees
    df[ df$value == 1 &
        df$row_col %in% on_fire$row_col, "value" ] <- 0.8
  
    saveRDS( df, file = paste0( FILE_PATH, "burn_step_", TIME, ".Rds" ) )
  
  }
  
  # plot data
  # define colors 
	my_cols <- c( "black", "#3f0000", "#580000", "#720000", "#d01c1f", "darkgreen" )
	names( my_cols ) <- c( 0, 0.2, 0.4, 0.6, 0.8, 1 )
  
  ggplot( df, aes( x = col_idx, y = row_idx, fill = as.factor( value ) ) ) +
    geom_raster() +
    scale_fill_manual( values = my_cols ) +
    scale_y_continuous( expand = c( 0, 0 ) ) +
    scale_x_continuous( expand = c( 0, 0 ) ) +
	theme( axis.title      = element_blank(),
           axis.text       = element_blank(),
           legend.position = "none",
           panel.grid      = element_blank(),
           axis.ticks      = element_blank(),
           plot.margin     = grid::unit( c(0,0,-1,-1), "mm") )
  
  

Contents