--- title: "fireSense tutorial" author: "Jean Marchal, Eliot McIntire, Tati Micheletti, Alex Chubaty" date: "`r Sys.Date()`" output: rmarkdown::html_vignette references: - id: Marchal2017 type: article-journal author: - family: Marchal given: Jean - family: Cumming given: Steve G. - family: McIntire given: Eliot J. B. issued: - year: '2017' month: '1' title: '[Exploiting Poisson additivity to predict fire frequency from maps of fire weather and land cover in boreal forests of Québec, Canada]{.nocase}' container-title: Ecography page: '200-209' volume: '40' issue: '1' abstract: Predictive models of fire frequency conditional on weather and land cover are essential to assess how future cover-type distributions and weather conditions may influence fire regimes. We modelled the effects of bottom-up variables (e.g. land cover) and top-down variables (e.g. fire weather) simultaneously with data aggregated or interpolated to spatial and temporal units of 100 km2 and 1 yr in the boreal forest of Québec, Canada. For models of human-caused fires, we used road density as a surrogate for human access and behaviour. We exploited the additive property of Poisson distributions to estimate cover-type specific fire count rates, which would normally not be possible with data of this spatial resolution. We used piecewise linear functions to model nonlinear relations between fire weather and fire frequency for each cover-type simultaneously. The estimated conditional rates may be considered as expected mean counts per unit area and time. It follows that these rates can be rescaled to arbitrary spatial and temporal extents. Our results showed fire frequency increased nonlinearly as aridity increased and more quickly in disturbed areas than other types. Road density exerted the strongest influence on the frequency of human-caused fires, which were positively correlated with road density. The estimates may be used to parameterize the fire ignition component of spatial simulation models, which often have a resolution different from that at which the data were collected. This is an essential step in incorporating biotic and abiotic feedbacks, land-cover dynamics, and climate projections into ecological forecasting. The insight into the power of Poisson additivity to reveal high-resolution ecological processes from low-resolution data could have applications in other areas of ecology. DOI: 10.1111/ecog.01849 ISSN: '09067590' - id: Marchal2019 type: article-journal author: - family: Marchal given: Jean - family: Cumming given: Steven G. - family: McIntire given: Eliot J. B. issued: - year: '2019' month: '5' title: '[Turning Down the Heat: Vegetation Feedbacks Limit Fire Regime Responses to Global Warming]{.nocase}' container-title: Ecosystems URL: http://link.springer.com/10.1007/s10021-019-00398-2 DOI: 10.1007/s10021-019-00398-2 ISSN: '1432-9840' - id: Schoenberg2003 type: article-journal author: - family: Schoenberg given: Frederic Paik - family: Peng given: Roger - family: Woods given: James issued: - year: '2003' month: '9' title: '[On the distribution of wildfire sizes]{.nocase}' container-title: Environmetrics page: '583-592' volume: '14' issue: '6' abstract: tapered Pareto keyword: Pareto,Tapered Pareto,Wildfires,pareto,tapered pareto,wildfires URL: http://doi.wiley.com/10.1002/env.605 DOI: 10.1002/env.605 ISSN: '1180-4009' vignette: > %\VignetteIndexEntry{fireSense tutorial} %\VignetteEncoding{UTF-8} %\VignetteEngine{knitr::rmarkdown} editor_options: chunk_output_type: console --- ```{r setup, include=FALSE} library(knitr) knitr::opts_chunk$set( collapse = TRUE, comment = "#>", echo = TRUE, eval = FALSE ## TODO: temporary until I can rework this vignette to actually run ) ``` ```{r init} library(Require) Require(c( "data.table", "dplyr", "pemisc", "PtProcess", "quickPlot", "raster", "sf", "sp", "SpaDES.core", "SpaDES.tools", "spatstat" )) options(reproducible.useCache = FALSE) # Turn off caching moduleDir <- checkPath(file.path(tempdir(), "modules"), create = TRUE) outputDir <- checkPath(file.path(tempdir(), "outputs"), create = TRUE) setPaths( modulePath = moduleDir, outputPath = outputDir ) ## TODO: download modules to moduleDir -- use git or downloadModule() ??? normalizeRaster <- function(x) { ## TODO: use pemisc version (#9) # Normalize raster values (x - minValue(x)) / (maxValue(x) - minValue(x)) } ``` # Introduction `fireSense` is a spatially explicit landscape fire model, that is, it simulates the fire disturbance in the landscape. `fireSense` is implemented as a group of `SpaDES` modules. A main asset of `fireSense` is that it can be parameterized directly from data using systematic methods while incorporating sensitivity to different environmental drivers. To better represent how environmental drivers control fire, `fireSense` breaks down the fire process into three stages: ignition, escape, and spread. Sensitivity to environmental drivers can be introduced at each of these stages and these factors need not necessarily be identical for all stages. This way each module can be adapted to your own problem and used to answer your own questions. In order to facilitate the parameterization of `fireSense` from data using systematic methods we have created SpaDES modules dedicated to these tasks: - `fireSense_IgnitionFit` - `fireSense_EscapeFit` - `fireSense_SpreadFit` These follow the way `fireSense` breaks down the fire process and are named accordingly. For example, `fireSense_IgnitionFit` implements methods to fit the statistical model of fire ignitions. The `fireSense_IgnitionFit` module implements methods described in Marchal et al. [-@Marchal2017], `fireSense_EscapeFit` and `fireSense_SpreadFit` implement methods described in Marchal et al. [-@Marchal2019]. Each of these modules is associated with an additional module to facilitate the production of predictive maps (rasters) or data sets (data.frames). To continue with the example of fire ignitions, the module `fireSense_IgnitionPredict` is associated with the `fireSense_IgnitionFit` module. One possible use of the `fireSense_IgnitionPredict` module would be to generate rasters describing in a spatially explicit way the predicted ignition rates. In addition of these modules, we implemented the `fireSense_SizeFit` module to fit statistical model of the fire size distribution using the tapered Pareto distribution [@Schoenberg2003]. The use of `fireSense_SizeFit` is not a requirement because these outputs are not required by `fireSense`. Although the use of `fireSense_SizeFit` is optional, it can be used to reduce the dimensionality of the problem, which helps speed up and facilitate convergence when using the . module later. Later in this tutorial we will show how the outputs of the `fireSense_SizeFit` module can be used as inputs of the `fireSense_SpreadFit` module. Alternatively, the parameter of the tapered Pareto distribution can be used as inputs of the spread model assuming these capture the effect of environmental drivers of fire sizes. We start by fitting the spread model using the `fireSense_SpreadFit` module. In order to fit this model we need data about the shape of the fire size distribution. We can predict it using the `fireSense_SizePredict` module and fit the model to our study region using the `fireSense_SizeFit` module. This tutorial is intended to provide examples to run all `fireSense` modules without the need to download actual data. Depending on the fact that some of them have dependencies. ```{r create_data, message=FALSE} # Create a raster template describing the study area raster_width <- raster_height <- 100 r_template <- raster(matrix(nrow = raster_height, ncol = raster_width)) # Create rasters describing environmental drivers controlling fire # Here we use for example the land cover and the weather set.seed(123) ## Landcover -- these should be "proportion of that single cover type", e.g., Proportion Deciduous landTypeOne <- gaussMap(r_template, scale = 300, var = 300) landTypeOne <- normalizeRaster(landTypeOne) landTypeTwo <- 1 - landTypeOne ## Weather weather <- gaussMap(r_template, scale = 300, var = 3000) # Create low resolution rasters of the environmental drivers # These are used for fitting the ignition model. The main advantages # of fire modeling using low resolution are described in Marchal et al. 2017. # In short, unlike high-resolution modeling, fire locations need not be known # precisely and do not require data at scales where local homogeneity of # landcover can be assumed. landTypeOneLowRes <- aggregate(landTypeOne, fact = 25, fun = mean) landTypeTwoLowRes <- 1 - landTypeOneLowRes weatherLowRes <- aggregate(weather, fact = 25, fun = mean) # Let's use a known pattern / equation nbFires <- 500 fireLocations <- rpoint(nbFires, f = function(x, y, ...) 300 * y) # Introduce some spatial variability: more fires in the North fireLocations <- as(fireLocations, "SpatialPoints") # landcoverDataLowRes <- extract(landTypeOneLowRes, fireLocations, cellnumbers = TRUE) %>% # as_tibble %>% # rename(cell_id = 1, landtype_1_pp = 2) %>% # Rename columns using column index # distinct(cell_id, landtype_1_pp) %>% # mutate(landtype_2_pp = 1 - landtype_1_pp) # Add a column with the 2nd landcover type # weatherDataLowRes <- extract(weatherLowRes, fireLocations, cellnumbers = TRUE) %>% # as_tibble %>% # rename(cell_id = 1, weather = 2) %>% # Rename columns using column index # distinct(cell_id, weather) # Create the dataset necessary to fit the statistical model of fire ignitions # dataFireSense_IgnitionFit <- landcoverDataLowRes %>% # group_by(cell_id) %>% # summarise(n_fires = n()) %>% # right_join(landcoverDataLowRes) %>% # left_join(weatherDataLowRes) %>% # mutate(n_fires = ifelse(is.na(n_fires), 0, n_fires)) # Converting the above from tibble to data.table dataFireSense_IgnitionFit <- data.table( extract(landTypeOneLowRes, fireLocations, cellnumbers = TRUE), landtype_2_pp = extract(landTypeTwoLowRes, fireLocations, cellnumbers = FALSE), weather = extract(weatherLowRes, fireLocations, cellnumbers = FALSE) ) setnames(dataFireSense_IgnitionFit, old = "layer", new = "landtype_1_pp") dataFireSense_IgnitionFit <- dataFireSense_IgnitionFit[, list( n_fires = .N, landtype_1_pp = landtype_1_pp[1], landtype_2_pp = landtype_2_pp[1], weather = weather[1] ), by = cells] dataFireSense_EscapeFit <- data.table( landtype_1_pp = extract(landTypeOne, fireLocations, cellnumbers = FALSE), landtype_2_pp = extract(landTypeTwo, fireLocations, cellnumbers = FALSE), weather = extract(weather, fireLocations, cellnumbers = FALSE) ) # Create the dataset necessary to fit the statistical model fire escapes # dataFireSense_EscapeFit <- tibble( # landtype_1_pp = extract(landTypeOne, fireLocations), # landtype_2_pp = extract(landTypeTwo, fireLocations), # weather = extract(weather, fireLocations) # ) b1 <- .01 b2 <- -1 b3 <- 1 escapeProb <- with(dataFireSense_EscapeFit, binomial()$linkinv(weather * b1 + landtype_1_pp * b2 + landtype_2_pp * b3)) escaped <- rbinom(n = nbFires, size = 1, prob = escapeProb) dataFireSense_EscapeFit[, `:=`(escaped = escaped, n_fires = 1)] # dataFireSense_EscapeFit <- mutate(dataFireSense_EscapeFit, escaped = escaped, n_fires = 1) # Create the dataset necessary to fit the statistical model of the fire size distribution # Calculate mean landcover and weather conditions around the locations of escaped fires buf_around_loc_escaped <- gBuffer(fireLocations[as.logical(escaped)], byid = TRUE, width = .03) landtypeTypeOneBufMn <- extract(landTypeOne, buf_around_loc_escaped) %>% lapply(mean) %>% unlist() landtypeTypeTwoBufMn <- extract(landTypeTwo, buf_around_loc_escaped) %>% lapply(mean) %>% unlist() weatherBufMn <- extract(weather, buf_around_loc_escaped) %>% lapply(mean) %>% unlist() dataFireSense_SizeFit <- data.table( landtype_1_pp = landtypeTypeOneBufMn, landtype_2_pp = landtypeTypeTwoBufMn, weather = weatherBufMn ) b1_l <- -.03 b2_l <- 4 b3_l <- -2 lambda <- with(dataFireSense_SizeFit, exp(weather * b1_l + landtype_1_pp * b2_l + landtype_2_pp * b3_l)) b1_t <- .02 b2_t <- 4 b3_t <- 2 theta <- with(dataFireSense_SizeFit, exp(weather * b1_t + landtype_1_pp * b2_t + landtype_2_pp * b3_t)) fireSize <- round(rtappareto(n = length(lambda), lambda = lambda, theta = theta, a = 1)) dataFireSense_SizeFit[, fire_size := fireSize] # dataFireSense_SizeFit <- mutate(dataFireSense_SizeFit, fire_size = fireSize) # Create the fire attribute dataset that describes the starting locations # and the size of the fires to be spread. This is needed to fit the statistical model of spread probabilities fireAttributesFireSense_SpreadFit <- SpatialPointsDataFrame(fireLocations[as.logical(escaped)], data = data.frame(size = fireSize) ) ``` ## Fit the statistical model of fire ignitions ```{r fit_ignition_model, message=FALSE} modules <- list("fireSense_IgnitionFit") times <- list(start = 1, end = 1) # Define fireSense_IgnitionFit module inputs objects <- list(dataFireSense_IgnitionFit = dataFireSense_IgnitionFit) # Define fireSense_IgnitionFit module parameters formula <- formula(n_fires ~ landtype_1_pp:weather + landtype_2_pp:weather - 1) family <- poisson(link = "identity") ub <- list(coef = 1) dataObjName <- "dataFireSense_IgnitionFit" trace <- 1 iterDEoptim <- 60 iterNlminb <- 100 cores <- 50 parameters <- list( fireSense_IgnitionFit = list( formula = formula, # Formula of the statistical model family = family, # Distribution family, here the negative binomial distribution ub = ub, # Upper bounds for coefficients to be estimated data = dataObjName, # Name of the data.frame containing the variables in the statistical model. By default "dataFireSense_IgnitionFit" trace = trace, # Print progress every 10 iterations iterDEoptim = iterDEoptim, # Integer defining the maximum number of iterations allowed for the DEoptim optimizer iterNlminb = iterNlminb, # Number of trials, or searches, to be performed by the nlminb optimizer in order to find the best solution cores = cores # Number of logical cores to use for parallel computation during optimization ) ) # Run the simulation sim <- simInitAndSpades( modules = modules, params = parameters, objects = objects, times = times ) fireSense_IgnitionFitted <- sim$fireSense_IgnitionFitted # Extract the fitted model from the sim object ``` ## Fit the statistical model of fire escapes ```{r fit_escape_model, message=FALSE} modules <- list("fireSense_EscapeFit") times <- list(start = 1, end = 1) # Define fireSense_EscapeFit module inputs objects <- list(dataFireSense_EscapeFit = dataFireSense_EscapeFit) # Define fireSense_EscapeFit module parameters formula <- formula(cbind(escaped, n_fires - escaped) ~ landtype_1_pp + landtype_2_pp + weather - 1) dataObjName <- "dataFireSense_EscapeFit" parameters <- list( fireSense_EscapeFit = list( formula = formula, # Formula of the statistical model data = dataObjName # Name of the data.frame containing the variables in the statistical model. By default "dataFireSense_EscapeFit" ) ) # Run the simulation sim <- simInitAndSpades( modules = modules, params = parameters, objects = objects, times = times ) fireSense_EscapeFitted <- sim$fireSense_EscapeFitted # Extract the fitted model from the sim object ``` ## Fit the statistical model of the spread probabilities The purpose of this step is to derive variable fire spread probabilities at the cell level, both spatially and temporally, which are sensitive to environmental drivers that control fire size. ## Fit the statistical model of the fire size distribution Although the use of fireSense_SizeFit is optional, it can be used to reduce the dimensionality of the problem, which helps speed up and facilitate convergence when using the fireSense_SpreadFit module later. Here, we use the fireSense_SizeFit module to model the relation between fire sizes and flammability conditions as represented by the drought index and the type of fuels. We then use the fireSense_SpreadFit module to convert the fitted equations produced by fireSense_SizeFit into variable probabilities of fire spread at the cell level, which are also sensitive to environmental drivers that control fire size. ```{r fit_size_distribution_model, message=FALSE} modules <- list("fireSense_SizeFit") times <- list(start = 1, end = 1) # Define fireSense_SizeFit module inputs objects <- list(dataFireSense_SizeFit = dataFireSense_SizeFit) # Define fireSense_SizeFit module parameters formula <- formula(fire_size ~ landtype_1_pp + landtype_2_pp + weather - 1) dataObjName <- "dataFireSense_SizeFit" parameters <- list( fireSense_SizeFit = list( formula = list( # Formulas of the statistical model beta = formula, # The formulas for the beta and theta parameters of the tapered Pareto. theta = formula # They do not have to be identical, even if it's the case here ), data = dataObjName, # Name of the data.frame containing the variables in the statistical model. By default "dataFireSense_SizeFit" link = list( beta = "log", # Link function for the beta parameter of the tapered Pareto theta = "log" # Link function for the theta parameter of the tapered Pareto ), a = 1, # Lower truncation point a of the tapered Pareto ub = list( beta = 10, theta = 10 ), itermax = 2000, trace = 100 ) ) # Run the simulation sim <- simInitAndSpades( modules = modules, params = parameters, objects = objects, times = times ) fireSense_SizeFitted <- sim$fireSense_SizeFitted # Extract the fitted model from the sim object ``` ### Produce the maps of the tapered Pareto parameters ```{r produce_maps_tp_params, message=FALSE} modules <- list("fireSense_SizePredict") times <- list(start = 1, end = 1) # Define fireSense_SizePredict module inputs objects <- list( fireSense_SizeFitted = fireSense_SizeFitted, landtype_1_pp = landTypeOne, landtype_2_pp = landTypeTwo, weather = weather ) # Define fireSense_SizePredict module outputs outputs <- rbind( data.frame( file = "fireSense_SizePredicted_Beta.tif", fun = "writeRaster", objectName = "fireSense_SizePredicted_Beta", package = "raster", saveTime = 1 ), data.frame( file = "fireSense_SizePredicted_Theta.tif", fun = "writeRaster", objectName = "fireSense_SizePredicted_Theta", package = "raster", saveTime = 1 ) ) # Define fireSense_SizePredict module parameters parameters <- list( fireSense_SizePredict = list( data = c("landtype_1_pp", "landtype_2_pp", "weather"), modelName = "fireSense_SizeFitted" # This is the default ) ) # Run the simulation sim <- simInitAndSpades( modules = modules, objects = objects, outputs = outputs, params = parameters, times = times ) ``` ### Use predicted parameters of the tapered Pareto as inputs to the statistical model of the spread probabilities Here, we use the `fireSense_SpreadFit` module to convert them into spatially variable spread probabilities. ```{r fit_spread_model, message=FALSE} modules <- list("fireSense_SpreadFit") times <- list(start = 1, end = 1) # Define fireSense_SpreadFit module inputs inputs <- rbind( # tapered Pareto's beta data.frame( files = dir( getPaths()$outputPath, pattern = "fireSense_SizePredicted_Beta", full.names = TRUE ), functions = "raster::raster", objectName = "beta", loadTime = 1 ), # tapered Pareto's theta data.frame( files = dir( getPaths()$outputPath, pattern = "fireSense_SizePredicted_Theta", full.names = TRUE ), functions = "raster::raster", objectName = "theta", loadTime = 1 ) ) objects <- list(fireAttributesFireSense_SpreadFit = fireAttributesFireSense_SpreadFit) # Define fireSense_SpreadFit module parameters formula <- formula(~ I(1 / beta) + log(theta) - 1) parameters <- list( fireSense_SpreadFit = list( formula = formula, # Formula of the statistical model data = c("beta", "theta"), lower = c(.01, 0, .1, .3, .001, .001), upper = c(.20, .1, 10, 4., .300, .300), cores = 8 ) ) # Run the simulation sim <- simInitAndSpades( inputs = inputs, modules = modules, objects = objects, params = parameters, times = times ) fireSense_SpreadFitted <- sim$fireSense_SpreadFitted # Extract the fitted model from the sim object ``` ## Predict maps of fire ignition rates, escape and spread probabilities ### Fire ignition rates ```{r predict_ignition_rates, message=FALSE} modules <- list("fireSense_IgnitionPredict") times <- list(start = 1, end = 1) # Define fireSense_IgnitionPredict module inputs objects <- list( fireSense_IgnitionFitted = fireSense_IgnitionFitted, landtype_1_pp = landTypeOne, landtype_2_pp = landTypeTwo, weather = weather ) # Define fireSense_IgnitionPredict module outputs outputs <- rbind( data.frame( file = paste0("fireSense_IgnitionPredicted.tif"), fun = "writeRaster", objectName = "fireSense_IgnitionPredicted", package = "raster", saveTime = 1 ) ) # Define fireSense_IgnitionPredict module parameters parameters <- list( fireSense_IgnitionPredict = list( data = c("landtype_1_pp", "landtype_2_pp", "weather"), modelName = "fireSense_IgnitionFitted" # This is the default ) ) # Run the simulation sim <- simInitAndSpades( modules = modules, objects = objects, outputs = outputs, params = parameters, times = times ) ``` ### Fire escape probabilities ```{r predict_escape_probabilities, message=FALSE} modules <- list("fireSense_EscapePredict") times <- list(start = 1, end = 1) # Define fireSense_EscapePredict module inputs objects <- list( fireSense_EscapeFitted = fireSense_EscapeFitted, landtype_1_pp = landTypeOne, landtype_2_pp = landTypeTwo, weather = weather ) # Define fireSense_EscapePredict module outputs outputs <- rbind( data.frame( file = paste0("fireSense_EscapePredicted.tif"), fun = "writeRaster", objectName = "fireSense_EscapePredicted", package = "raster", saveTime = 1 ) ) # Define fireSense_EscapePredict module parameters parameters <- list( fireSense_EscapePredict = list( data = c("landtype_1_pp", "landtype_2_pp", "weather"), modelName = "fireSense_EscapeFitted" # This is the default ) ) # Run the simulation sim <- simInitAndSpades( modules = modules, objects = objects, outputs = outputs, params = parameters, times = times ) ``` ### Fire spread probabilities ```{r predict_spread_probabilities, message=FALSE} modules <- list("fireSense_SpreadPredict") times <- list(start = 1, end = 1) # Define fireSense_SpreadPredict module inputs # tapered Pareto's beta inputs <- rbind( data.frame( files = dir( getPaths()$outputPath, pattern = "fireSense_SizePredicted_Beta", full.names = TRUE ), functions = "raster::raster", objectName = "beta", loadTime = 1 ), # tapered Pareto's theta data.frame( files = dir( getPaths()$outputPath, pattern = "fireSense_SizePredicted_Theta", full.names = TRUE ), functions = "raster::raster", objectName = "theta", loadTime = 1 ) ) objects <- list(fireSense_SpreadFitted = fireSense_SpreadFitted) # Define fireSense_SpreadPredict module outputs outputs <- rbind( data.frame( file = paste0("fireSense_SpreadPredicted.tif"), fun = "writeRaster", objectName = "fireSense_SpreadPredicted", package = "raster", saveTime = 1 ) ) # Define fireSense_SpreadPredict module parameters parameters <- list( fireSense_SpreadPredict = list( data = c("landtype_1_pp", "landtype_2_pp", "weather"), modelName = "fireSense_SpreadFitted" # This is the default ) ) # Run the simulation sim <- simInitAndSpades( modules = modules, objects = objects, outputs = outputs, params = parameters, times = times ) ``` ## Run fireSense, finally! ```{r run_fireSense, message=FALSE} modules <- list("fireSense") times <- list(start = 1, end = 1) # Define fireSense module inputs inputs <- rbind( # Fire ignition rates data.frame( files = dir( getPaths()$outputPath, pattern = "fireSense_IgnitionPredicted", full.names = TRUE ), functions = "raster::raster", objectName = "ignitionProbRaster", loadTime = 1 ), # Fire escape probabilities data.frame( files = dir( getPaths()$outputPath, pattern = "fireSense_EscapePredicted", full.names = TRUE ), functions = "raster::raster", objectName = "escapeProbRaster", loadTime = 1 ), # Fire spread probabilities data.frame( files = dir( getPaths()$outputPath, pattern = "fireSense_SpreadPredicted", full.names = TRUE ), functions = "raster::raster", objectName = "spreadProbRaster", loadTime = 1 ) ) # Run the simulation sim <- simInitAndSpades( inputs = inputs, modules = modules, objects = objects, times = times ) burnMap <- sim$burnMap Plot(burnMap) ``` Note that all the steps above could be done in one simulation / SpaDES call. # Additional resources Read modules metadata, all module parameters are documented in `.Rmd` files. # References