Nothing
#' @useDynLib steps
#' @importFrom Rcpp sourceCpp
NULL
#' How the population disperses in a landscape.
#'
#' Pre-defined or custom functions to define population dispersal during a
#' simulation. Each dispersal method uses different computing resources
#' and may be applicable to different simulation scenarios. Please see the
#' tutorial vignette titled "Creating custom *steps* functions" for
#' information on how to write custom functions for use in simulations.
#'
#' @name population_dispersal_functions
#'
#' @seealso
#' \itemize{
#' \item{\link[steps]{kernel_dispersal} for kernel-based diffusion dispersal using
#' habitat suitability and/or carrying capacity to influence movements}
#' \item{\link[steps]{cellular_automata_dispersal} for individual-based movements using
#' rule-sets}
#' \item{\link[steps]{fast_dispersal} for quick kernel-based diffusion
#' dispersal without accounting for spatial heterogeneity}
#' }
NULL
#' Kernel-based dispersal
#'
#' The kernel_dispersal function employs a probabilistic
#' kernel-based dispersal algorithm to modify the population
#' using a user-defined diffusion distribution (see
#' \link[steps]{dispersal_kernel}), arrival probability layers
#' (e.g. habitat suitability), and growth limiting layers (e.g.
#' carrying capacity). This function is much slower than the
#' \link[steps]{fast_dispersal}, however, respects dispersal
#' limitations which may be more ecologically appropriate. Further,
#' the kernel-based dispersal function utilises a mechanism to
#' optimise computational performance in which it switches between
#' pre-allocating cell movements based on the available memory of
#' the host computer (faster but more memory intensive) or executing
#' cell movements in sequence (slower but less memory intensive).
#'
#' @param dispersal_kernel a single built-in or user-defined distance dispersal
#' kernel function.
#' @param max_distance the maximum distance that each life stage can
#' disperse in spatial units of the landscape (in kernel-based dispersal
#' this truncates the dispersal curve). Setting a reasonable number will
#' increase the performance of a simulation by reducing the number of cells
#' that need to be calculated in distance matrices.
#' @param arrival_probability the name of a spatial layer in the landscape object
#' that controls where individuals can disperse to (e.g. "suitability") or
#' "none" to allow individuals to disperse to all non-NA cells. The default is
#' to use both the habitat suitability and carrying capacity layers. When this
#' option is selected, the arrival probability in each cell is calculated by
#' multiplying the habitat suitability by one minus the proportion of space taken
#' up in the cell (total population of life stages contributing to density
#' dependence divided by the carrying capacity).
#' @param dispersal_proportion a built-in or custom function defining the proportions
#' of individuals that can disperse in each life stage.
#'
#' @export
#'
#' @importFrom memuse Sys.meminfo mu.size
#'
#' @examples
#'
#' # Example of kernel-based dispersal where only the 3rd life stage
#' # disperses up to a maximum distance of 2000 meters. Dispersal is affected
#' # by both habitat suitability and carrying capacity (default). The default
#' # dispersal kernel uses a decay parameter to control how far populations disperse.
#'
#' \dontrun{
#' kb_dispersal <- kernel_dispersal(max_distance = 2000,
#' dispersal_kernel = exponential_dispersal_kernel(distance_decay = 1000))
#'
#' ls <- landscape(population = egk_pop, suitability = egk_hab, carrying_capacity = egk_k)
#'
#' pd <- population_dynamics(change = growth(egk_mat),
#' dispersal = kb_dispersal,
#' density_dependence = ceiling_density())
#'
#' simulation(landscape = ls, population_dynamics = pd, habitat_dynamics = NULL, timesteps = 20)
#' }
kernel_dispersal <- function (dispersal_kernel = exponential_dispersal_kernel(distance_decay = 1),
max_distance = NULL,
arrival_probability = c("both", "suitability", "carrying_capacity", "none"),
dispersal_proportion = set_proportion_dispersing()) {
arrival_probability <- match.arg(arrival_probability)
pop_dynamics <- function(landscape, timestep) {
n_rows <- raster::nrow(landscape$population[[1]])
n_cols <- raster::ncol(landscape$population[[1]])
res <- raster::res(landscape$population[[1]])
default_max <- sqrt( (n_cols * res[1])^2 + (n_rows * res[2])^2 )
bad_distance <- FALSE
if (length(max_distance) > 1) {
stop("max_distance must be NULL, Inf, or a positive number")
}
# estimate a max distance by default
if (is.null(max_distance)) {
# get relative dispersal probabilities on a mesh with similar resolution to the raster
distances <- seq(0, default_max, by = max(res))
probs <- dispersal_kernel(distances)
probs <- probs / sum(probs)
# get approximate cumulative probability of dispersing to each of these distances
cum_probs <- cumsum(probs)
# find distances beyond which there is negligible probability of dispersal,
# or return the maximum landscape dimension if we couldn't find one
beyond_limit <- cum_probs > (1 - 1e-6)
if (any(beyond_limit)) {
# find the first beyond
max_distance <- distances[which(beyond_limit)]
} else {
max_distance <- default_max
}
} else {
# if they passed in a number
if (is.finite(max_distance)) {
# cap it at the maximum
if (max_distance > default_max) {
warning("The provided maximum distance was beyond the largest distance in the landscape; ",
"the largest possible dispersal distance will be used.")
max_distance <- default_max
}
# error on negative numbers
if (max_distance < 0) {
bad_distance <- TRUE
}
} else {
# non-finite case, check it's an Inf
if (identical(max_distance, Inf)) {
max_distance <- default_max
} else{
bad_distance <- TRUE
}
}
}
# warn if there was a problem with the distance they entered
if (bad_distance) {
stop("max_distance must be NULL, Inf, or a positive number")
}
distance_list <- steps_stash$distance_list
if (is.null(distance_list)) {
# what are dimensions of raster (lazyeval was causing this to be rerun on
# every contribute() call! so force execution of this here)
raster_dim <- dim(landscape$population[[1]])[-3]
raster_dim <- force(raster_dim)
distance_info <- get_distance_info(res = raster::res(landscape$population),
max_distance = max_distance)
sys_mem_available <- memuse::Sys.meminfo()$freeram
sys_mem_available <- memuse::mu.size(sys_mem_available, as.is = FALSE) * 0.8
n_elem <- nrow(distance_info) * raster::ncell(landscape$population)
sys_mem_required <- (n_elem * (64 + 32)) / 8
if (sys_mem_required < sys_mem_available) {
print("Kernel-based dispersal utilising available RAM to speed up operations")
distance_list <- steps_stash$distance_list <- lapply(seq_len(raster::ncell(landscape$population)),
function (x) get_ids_dists(cell_id = x,
distance_info = distance_info,
raster_dim = raster_dim))
}
}
# how many life stages?
n_stages <- raster::nlayers(landscape$population)
# create masking layer
mask <- raster::getValues(landscape$population[[1]])
mask[!is.na(mask)] <- 1
# check the required landscape rasters/functions are available
layers <- arrival_probability
if (layers == "both") {
layers <- c("suitability", "carrying_capacity")
missing_layers <- vapply(landscape[layers], is.null, FUN.VALUE = FALSE)
if (any(missing_layers)) {
missing_text <- paste(paste("a", layers[missing_layers], "raster"),
collapse = " and ")
stop ("kernel_dispersal requires landscape to have ", missing_text,
call. = FALSE)
}
}
# find out which stages contribute to density. population dynamics will have
# copied this information over from the population density dependence
# function, if it was specified. Otherwise, use all stages
if (!exists("density_dependence_stages") | is.null(get0("density_dependence_stages"))) {
density_dependence_stages <- seq_len(n_stages)
}
dispersal_proportion <- dispersal_proportion(landscape, timestep)
# Which stages can disperse
which_stages_disperse <- which(dispersal_proportion > 0)
# get non-NA cells
cell_idx <- which(!is.na(raster::getValues(landscape$population[[1]])))
delayedAssign(
"habitat_suitability_values",
if (raster::nlayers(landscape$suitability) > 1) raster::getValues(landscape$suitability[[timestep]])
else raster::getValues(landscape$suitability)
)
if ("carrying_capacity" %in% layers) {
# 22.01.20 - # cc <- get_carrying_capacity(landscape, timestep)
cc <- landscape$carrying_capacity # 22.01.20
if (is.null(cc)) {
stop ("carrying capacity must be specified in the landscape object to use carrying capacity arrival probabilities",
call. = FALSE)
}
delayedAssign(
"carrying_capacity_proportion",
raster::getValues(
raster::calc(
raster::stack(landscape$population)[[
density_dependence_stages
]],
sum
) / cc
)
)
cc_values <- raster::getValues(cc)
} else {
cc_values <- NULL
density_dependence_stages <- NULL
}
arrival_prob_values <- switch(
arrival_probability,
both = habitat_suitability_values * (1 - carrying_capacity_proportion),
suitability = habitat_suitability_values,
carrying_capacity = (1 - carrying_capacity_proportion),
none = mask
)
# Only non-zero arrival prob cells can receive individuals
can_arriv_ids <- which(arrival_prob_values > 0 & !is.na(arrival_prob_values))
# Extract the population values
pop <- raster::getValues(landscape$population)
# store the original population, so that individuals don't disperse twice
original_pop <- pop
# index all pop origins and stages where there is at least one individual
indices <- which(pop > 0 & !is.na(pop), arr.ind = TRUE)
# subset to stages that disperse
indices <- indices[indices[, 2] %in% which_stages_disperse, , drop = FALSE]
for(row in sample.int(nrow(indices))) {
pop <- disperse(origin = indices[row, 1],
stage = indices[row, 2],
pop = pop,
original_pop = original_pop,
prop_dispersing = dispersal_proportion,
can_arriv_ids = can_arriv_ids,
arrival_prob_values = arrival_prob_values,
dispersal_kernel = dispersal_kernel,
carrying_capacity = cc_values,
density_dependence_stages = density_dependence_stages,
total_stages = n_stages,
distance_list = distance_list,
distance_info = distance_info,
raster_dim = raster_dim)
}
landscape$population[] <- pop
#cat("Pre-Post Population:", poptot, sum(raster::cellStats(landscape$population, sum)), "(Timestep", timestep, ")", "\n")
landscape
}
as.population_dispersal(pop_dynamics)
}
#' Cellular automata dispersal
#'
#' The cellular_automata_dispersal function simulates movements of
#' individuals using rule-based cell movements. In each cell that has
#' population, every individual up to a specified proportion of the
#' total population attempts to move. For each step from a specified minimum up
#' to a specified maximum number of movements, a weighted draw of four
#' directions, based on habitat suitability, is made and then the destination
#' cell is checked for available carrying capacity. If there is carrying capacity
#' available, the individual moves to the cell, if not, it remains in its current
#' cell. This is repeated until the maximum number of cell movements is reached.
#' If no cell is found with available carrying capacity, the individual remains
#' in the source cell.
#'
#' This function allows the use of barriers in the landscape to influence
#' dispersal. The function is computationally efficient, however, because
#' as individuals are dispersed, performance scales with the population sizes
#' in each cell across a landscape and the maximum number of cell movements.
#'
#' The maximum number of cell movements in cellular automata dispersal does not
#' correspond exactly to the distance decay of a dispersal kernel, since cellular
#' automata dispersal depends on the permeability of the landscape, and is
#' interrupted on reaching a cell with available capacity (above the minimum
#' specified number of cell movements). A heuristic that can be used to determine
#' a reasonable number of steps from a mean dispersal distance `d` and cell
#' resolution `res` is: `max_cells = round(2 * (d / (res * 1.25)) ^ 2)`. This
#' corresponds approximately to the number of cell-steps in an infinite,
#' homogenous landscape with no early stopping, for which d is the mean
#' end-to-end dispersal distance of all individuals.
#'
#' Rather than relying on this value, we recommend that the user experiment with
#' the \code{max_cells} and \code{min_cells} parameters to find a value such that
#' the the mean dispersal distance in a reasonably realistic simulation
#' corresponds with field estimates of mean dispersal distances.
#'
#' @param max_cells the maximum number of cell movements that each individual in
#' each life stage can disperse in whole integers.
#' @param min_cells the minimum number of cell movements that each individual in
#' each life stage will disperse in whole integers.
#' @param dispersal_proportion a built-in or custom function defining the proportions
#' of individuals that can disperse in each life stage.
#' @param barriers the name of a spatial layer in the landscape object that
#' contains cell values between 0 (no barrier) and 1 (full barrier) Any
#' values between 0 and 1 indicate the permeability of the barrier.
#' @param use_suitability should habitat suitability be used to control the
#' likelihood of individuals dispersing into cells? The default is TRUE. Note,
#' if a barrier map is also provided, the suitability map is multiplied with
#' the barrier map to generate a permeability map of the landscape.
#' @param carrying_capacity the name of a spatial layer in the landscape object
#' that specifies the carrying capacity in each cell.
#'
#' @export
#'
#' @examples
#'
#' # Example of cellular automata dispersal where the 2nd and 3rd life stages
#' # disperse up to a maximum of 100 cells but dispersal is affected by
#' # barriers (in this case roads). The road rasters have values of 0 for
#' # large roads (no dispersal across barrier) and 0.5 for smaller roads
#' # (reduced dispersal across barrier).
#'
#' \dontrun{
#' ca_dispersal <- cellular_automata_dispersal(max_cells = c(0, 100, 100), barriers = "roads")
#'
#' ls <- landscape(population = egk_pop,
#' suitability = egk_hab,
#' carrying_capacity = egk_k,
#' "roads" = egk_road)
#'
#' pd <- population_dynamics(change = growth(egk_mat),
#' dispersal = ca_dispersal,
#' density_dependence = ceiling_density())
#'
#' simulation(landscape = ls, population_dynamics = pd, habitat_dynamics = NULL, timesteps = 20)
#' }
cellular_automata_dispersal <- function (max_cells = Inf,
min_cells = max_cells, ### Added 07.02.22
dispersal_proportion = set_proportion_dispersing(),
barriers = NULL,
use_suitability = TRUE,
carrying_capacity = "carrying_capacity") {
# are there suitability and carrying_capacity landscape objects specified?
carrying_cap <- identical(carrying_capacity, "carrying_capacity")
pop_dynamics <- function (landscape, timestep) {
# read population raster stack from landscape
population_raster <- landscape$population
# get non-NA cells
idx <- which(!is.na(raster::getValues(population_raster[[1]])))
# is suitability specified without raster existing in landscape?
if (use_suitability & is.null(landscape$suitability)) {
stop("A habitat suitability raster is missing from the landscape object")
}
# handle input suitability raster stacks
if (raster::nlayers(landscape$suitability) > 1) {
suitability_map <- landscape$suitability[[timestep]]
} else {
suitability_map <- landscape$suitability
}
# is carrying_capacity specified without raster existing in landscape?
if (carrying_cap & is.null(landscape$carrying_capacity)) {
stop("A carrying capacity object is missing from the landscape object")
}
# handle carrying_capacity as raster, function, or other spatial object in
# landscape
if (carrying_cap) {
# 22.01.20 - # cc <- get_carrying_capacity(landscape, timestep)
cc <- landscape$carrying_capacity # 22.01.20
} else {
cc <- landscape[[carrying_capacity]]
}
# do the minimum and maximum values have the same length? - Added 07.02.22
if (length(min_cells) != length(max_cells)) {
stop("Minimum cells and maximum cells must have the same length (i.e. number of life stages)")
}
# get population as a matrix
population <- raster::extract(population_raster, idx)
# get number of life-stages
n_stages <- raster::nlayers(population_raster)
# work out dispersal proportions for eligible life-stages with input function
dispersal_proportion <- dispersal_proportion(landscape, timestep)
# if max_cells is the default (Infinite), rescale to maximum distance of landscape
default_distance <- identical(max_cells, Inf)
if (default_distance) {
n_rows <- raster::nrow(population_raster[[1]])
n_cols <- raster::ncol(population_raster[[1]])
res <- raster::res(population_raster[[1]])
max_cells <- round(2 * (max(n_rows, n_cols) / (res * 1.25)) ^ 2)
min_cells <- max_cells ### Added 07.02.22
}
# handle dispersal distances as both scalars and vectors
if(!default_distance) {
warn_once(length(max_cells) < n_stages | length(max_cells) > n_stages,
paste(n_stages,
"life stages exist but",
length(max_cells),
"maximum cell movement(s) of",
paste(max_cells, collapse = ", "),
"were specified.\nAll life stages will use this distance."),
warning_name = "dispersal_distances")
warn_once(length(min_cells) < n_stages | length(min_cells) > n_stages, ### Added 07.02.22
paste(n_stages,
"life stages exist but",
length(min_cells),
"minimum cell movement(s) of",
paste(min_cells, collapse = ", "),
"were specified.\nAll life stages will use this distance."),
warning_name = "dispersal_distances")
}
if (length(max_cells) < n_stages | length(max_cells) > n_stages) {
max_cells <- rep_len(max_cells, n_stages)
}
if (length(min_cells) < n_stages | length(min_cells) > n_stages) { ### Added 07.02.22
min_cells <- rep_len(min_cells, n_stages)
}
# identify dispersing stages
which_stages_disperse <- which(dispersal_proportion > 0 & max_cells > 0 & min_cells > 0) ### Revised 07.02.22
# if no barrier map is specified, create a barriers matrix with all zeros.
if (is.null(barriers)) {
barriers_map <- raster::calc(population_raster[[1]],
function(x){x[!is.na(x)] <- 0; return(x)})
} else {
if (raster::nlayers(landscape[[barriers]]) > 1) {
barriers_map <- landscape[[barriers]][[timestep]]
} else {
barriers_map <- landscape[[barriers]]
}
}
if (use_suitability) {
permeability_map <- suitability_map * (1 - barriers_map)
} else {
permeability_map <- 1 - barriers_map
}
steps_stash$dispersal_stats_success <- replicate(n_stages, NULL, simplify = FALSE)
steps_stash$dispersal_stats_failure <- replicate(n_stages, NULL, simplify = FALSE)
# could do this in parallel
for (i in which_stages_disperse){
dispersed <- rcpp_dispersal(raster::as.matrix(population_raster[[i]]),
raster::as.matrix(cc),
raster::as.matrix(permeability_map),
as.integer(max_cells[i]),
as.integer(min_cells[i]), ### Added 07.02.22
as.numeric(dispersal_proportion[i]))
population_raster[[i]][] <- dispersed$future_population
steps_stash$dispersal_stats_success[[i]] <- dispersed$dispersed
steps_stash$dispersal_stats_failure[[i]] <- dispersed$failed
}
landscape$population <- population_raster
#cat("Pre-Post Population:", poptot, sum(raster::cellStats(landscape$population, sum)), "(Timestep", timestep, ")", "\n")
landscape
}
as.population_dispersal(pop_dynamics)
}
#' Fast diffusion-based dispersal
#'
#' The fast_dispersal function uses kernel-based dispersal
#' to modify the population with a user-defined diffusion distribution
#' and a fast-fourier transformation (FFT) computational algorithm. It
#' is computationally efficient and very fast, however, only useful for
#' situations where dispersal barriers or arrival based on habitat or
#' carrying capacity are not required (e.g. a homogeneous landscape or
#' where diffusion alone is sufficient to explain dispersal patterns).
#' Dispersal is not constrained to suitable habitat or available carrying
#' capacity.
#'
#' @param dispersal_kernel a single built-in or user-defined distance dispersal
#' kernel function.
#' @param dispersal_proportion a built-in or custom function defining the proportions
#' of individuals that can disperse in each life stage.
#'
#' @export
#'
#' @examples
#'
#' # Example of fast kernel-based dispersal where all life stages disperse.
#' # The default dispersal kernel uses a decay parameter to control how far
#' # populations disperse. Note proportions of populations to disperse are
#' # controlled by approach to carrying capacity.
#'
#' \dontrun{
#' fft_dispersal <- fast_dispersal(dispersal_proportion = density_dependence_dispersing(),
#' dispersal_kernel = exponential_dispersal_kernel(distance_decay = 1000))
#'
#' ls <- landscape(population = egk_pop, suitability = egk_hab, carrying_capacity = egk_k)
#'
#' pd <- population_dynamics(change = growth(egk_mat),
#' dispersal = fft_dispersal,
#' density_dependence = ceiling_density())
#'
#' simulation(landscape = ls, population_dynamics = pd, habitat_dynamics = NULL, timesteps = 20)
#' }
fast_dispersal <- function(dispersal_kernel = exponential_dispersal_kernel(distance_decay = 0.1),
dispersal_proportion = set_proportion_dispersing()) {
pop_dynamics <- function(landscape, timestep) {
n_stages <- raster::nlayers(landscape$population)
dispersal_proportion <- dispersal_proportion(landscape, timestep)
# Which stages can disperse
which_stages_disperse <- which(dispersal_proportion > 0)
# Apply dispersal to the population
# (need to run this separately for each stage)
for (stage in which_stages_disperse) {
pop <- landscape$population[[stage]]
pop_dispersing <- landscape$population[[stage]] * dispersal_proportion[[stage]]
pop_staying <- pop - pop_dispersing
# round population staying
idx <- not_missing(pop_staying)
pop_staying_vec <- raster::extract(pop_staying, idx)
pop_staying_vec <- round_pop(pop_staying_vec)
pop_staying[idx] <- pop_staying_vec
pop_dispersed <- dispersalFFT(
popmat = raster::as.matrix(
pop_dispersing
),
fs = setupFFT(
x = seq_len(raster::ncol(landscape$population)),
y = seq_len(raster::nrow(landscape$population)),
f = function(d) {
disp <- dispersal_kernel(d)
disp / sum(disp)
}
)
)
pop_dispersing[] <- pop_dispersed
pop <- pop_staying + pop_dispersing
landscape$population[[stage]] <- pop
}
#cat("Pre-Post Population:", poptot, sum(raster::cellStats(landscape$population, sum)), "(Timestep", timestep, ")", "\n")
landscape
}
as.population_dispersal(pop_dynamics)
}
##########################
### internal functions ###
##########################
as.population_dispersal <- function (dispersal) {
as_class(dispersal, "population_dispersal", "function")
}
# as.population_fast_dispersal <- function (fast_dispersal, info = NULL) {
# as_class(fast_dispersal, "population_fast_dispersal", "function", info = info)
# }
#
# as.population_kernel_dispersal <- function (kernel_dispersal, info = NULL) {
# as_class(kernel_dispersal, "population_kernel_dispersal", "function", info = info)
# }
#
# as.population_ca_dispersal <- function (ca_dispersal, info = NULL) {
# as_class(ca_dispersal, "population_ca_dispersal", "function", info = info)
# }
extend <- function (x, factor = 2) {
# given an evenly-spaced vector `x` of cell centre locations, extend it to the
# shortest possible vector that is at least `factor` times longer, has a
# length that is a power of 2 and nests the vector `x` approximately in the
# middle. This is used to define each dimension of a grid mapped on a torus
# for which the original vector x is approximately a plane.
# get cell number and width
n <- length(x)
width <- x[2] - x[1]
# the smallest integer greater than or equal to than n * factor and an
# integer power of 2
n2 <- 2 ^ ceiling(log2(factor * n))
# find how much to pad each end of n
pad <- n2 - n
if (pad %% 2 == 0) {
# if it's even then that's just tickety boo
pad1 <- pad2 <- pad / 2
} else {
# otherwise put the spare cell on the right hand side
pad1 <- (pad - 1) / 2
pad2 <- (pad + 1) / 2
}
# define the padding vectors
padx1 <- x[1] - rev(seq_len(pad1)) * width
padx2 <- x[n] + seq_len(pad2) * width
# combine these and add an attribute returning the start and end indices for
# the true vector
newx <- c(padx1, x, padx2)
attr(newx, 'idx') <- pad1 + c(1, n)
newx
}
bcb <- function (x, y, f = I) {
# get a the basis vector for a block-circulant matrix representing the
# dispersal matrix between equally-spaced grid cells on a torus, as some
# function `f` of euclidean distance. `x` and `y` are vectors containing
# equally-spaced vectors for the x and y coordinates of the grid cells on a
# plane (i.e. the real coordinates). These should have been extended in order
# to approximate some centre portion as a 2D plane.
# number and dimension of grid cells
m <- length(x)
n <- length(y)
x_size <- x[2] - x[1]
y_size <- y[2] - y[1]
# create indices for x and y on the first row of the dispersal matrix
xidx <- rep(1:m, n)
yidx <- rep(1:n, each = m)
# project onto the torus and get distances from the first cell, in each
# dimension
xdist <- abs(xidx - xidx[1])
ydist <- abs(yidx - yidx[1])
xdist <- pmin(xdist, m - xdist) * x_size
ydist <- pmin(ydist, n - ydist) * y_size
# flatten distances into Euclidean space, apply the dispersal function and
# return
d <- sqrt(xdist ^ 2 + ydist ^ 2)
f(d)
}
setupFFT <- function (x, y, f, factor = 2) {
# set up the objects needed for the FFT dispersal matrix representation
# extend the vectors (to project our plane on <= 1/4 of a torus)
xe <- extend(x, factor)
ye <- extend(y, factor)
# get indices to true vectors
xidx <- seq_range(attr(xe, 'idx'))
yidx <- seq_range(attr(ye, 'idx'))
# get fft basis for dispersal on a torus
bcb_vec <- bcb(ye, xe, f)
# create an empty population on all grid cells of the torus
pop_torus <- matrix(0, length(ye), length(xe))
# return as a named list for use in each iteration
list(bcb_vec = bcb_vec,
pop_torus = pop_torus,
xidx = xidx,
yidx = yidx)
}
dispersalFFT <- function (popmat, fs) {
# multiply the population matrix `popmat` giving the population of this stage
# in each cell through the dispersal matrix over the landscape, efficiently,
# and without ever having to construct the full matrix, by representing the
# landscape efficiently as a section of a torus and using linear algebra
# identities of the fast Fourier transform
# duplicate popmat to create 'before' condition
popmat_orig <- popmat
missing <- is.na(popmat)
# check for missing values and replace with zeros
if (any(missing)) {
popmat[missing] <- 0
}
# insert population matrix into the torus population
fs$pop_torus[fs$yidx, fs$xidx] <- popmat
# project population dispersal on the torus by fft
# get spectral representations of the matrix & vector & compute the spectral
# representation of their product
pop_fft <- stats::fft(t(fs$pop_torus))
bcb_fft <- stats::fft(fs$bcb_vec)
pop_new_torus_fft <- stats::fft(pop_fft * bcb_fft, inverse = TRUE)
# convert back to real domain, apply correction and transpose
pop_torus_new <- t(Re(pop_new_torus_fft) / length(fs$pop_torus))
pop_torus_new <- pmax(pop_torus_new, 0)
# extract the section of the torus representing our 2D plane and return
pop_new <- pop_torus_new[fs$yidx, fs$xidx]
# return NA values to matrix
pop_new[missing] <- NA
# get proportion of population that dispersed into valid (non-NA, inside the
# plane) cells
prop_in <- sum(pop_new, na.rm = TRUE) / sum(popmat_orig, na.rm = TRUE)
# increase all non-NA cells by inverse of proportion in valid cells (if proportion is valid number)
if (!is.nan(prop_in)) {
pop_new[!missing] <- pop_new[!missing] / prop_in
}
# make sure none are lost or gained (unless all are zeros)
if (any(pop_new[!missing] > 0)) {
pop_new[!missing] <- round_pop(pop_new[!missing])
}
pop_new
}
seq_range <- function (range, by = 1) seq(range[1], range[2], by = by)
# compute the *relative* distances to all neighbouring cells within a maximum
# distance
get_distance_info <- function(res, max_distance) {
width <- ceiling(max_distance / min(res)) + 1
id <- (1:width) - 1
xy <- expand.grid(id * res[1], id * res[2])
cell_coord <- expand.grid(id, id)
cell_coord <- as.matrix(cell_coord)
colnames(cell_coord) <- NULL
dists <- as.matrix(stats::dist(xy))[1, ]
keep <- dists < max_distance
# relative coordinates of cells that are within the distance
# ur <- cell_coord[keep, , drop = FALSE]
ur <- cell_coord[keep, ]
ul <- cbind(-ur[, 1], ur[, 2])
ll <- -ur
lr <- cbind(ur[, 1], -ur[, 2])
# all coordinates in the circle
coords <- rbind(ur, ul, ll, lr)
# add on their distances & remove duplicates
unique(cbind(coords, rep(dists[keep], 4)))
}
# given a cell id, find the coordinates (in number of cells from the origin)
id2coord <- function (id, dim) {
# how many rows have been covered
row <- id %/% dim[2]
# how many columns have been covered in the most recent row
col <- id %% dim[2]
# combine
coord <- cbind(col, (row + 1))
# make sure they are within the raster (for completeness)
max_id <- prod(dim)
invalid_id <- id > max_id | id < 1
coord[invalid_id, ] <- NA
coord
}
# returns NAs where the coords are outside the raster. coord must be a 2-column
# matrix
coord2id <- function (coord, dim) {
start_of_row <- (coord[, 2] - 1) * dim[2]
id <- start_of_row + coord[, 1]
# find coordinates outside the raster
bad_row <- coord[, 2] < 1 | coord[, 2] > dim[1]
bad_col <- coord[, 1] < 1 | coord[, 1] > dim[2]
invalid <- bad_row | bad_col
id[invalid] <- NA
id
}
# given a cell number, compute the cell ids that are within the maximum
# distance, and get the distances to those
get_ids_dists <- function(cell_id, distance_info, raster_dim) {
# get origin coordinate
origin_coord <- id2coord(cell_id, raster_dim)
# relative coordinates (in numbers of cells) of cells within max distance, and
# their distances from this cell
rel_coords <- distance_info[, 1:2]
dists <- distance_info[, 3]
# compute absolute coordinates
abs_coords <- sweep(rel_coords, 2, origin_coord, "+")
cell_ids <- coord2id(abs_coords, raster_dim)
valid <- !is.na(cell_ids)
# return a 2-column matrix of cells id and distances for all cells within the
# maximum distances, and within the raster
cbind(cell_ids[valid], dists[valid])
}
disperse <- function (origin,
stage,
pop,
original_pop,
prop_dispersing,
can_arriv_ids,
arrival_prob_values,
dispersal_kernel,
carrying_capacity = NULL,
density_dependence_stages = NULL,
total_stages = NULL,
distance_list = NULL,
distance_info = NULL,
raster_dim = NULL) {
if (is.null(distance_list)) {
print("Kernel-based dispersal running in single iteration mode to conserve RAM")
destinations <- get_ids_dists(cell_id = origin,
distance_info = distance_info,
raster_dim = raster_dim)
} else {
destinations <- distance_list[[origin]]
}
destination_ids <- destinations[, 1]
destination_dists <- destinations[, 2]
# index destination cells that allow arrival in raster
destination_index <- fast_match(destination_ids, can_arriv_ids)
if (length(destination_index) == 0) {
return(pop)
}
# subset destination ids and distances to valid arrival cells
destination_ids <- destination_ids[destination_index]
destination_dists <- destination_dists[destination_index]
prob <- dispersal_kernel(destination_dists)
# probability of dispersing multiplied by probability of arrival
prob <- prob * arrival_prob_values[destination_ids]
# standardise probabilities & check for NaN values
# prob <- ifelse(!prob, 0, prob / sum(prob))
prob <- prob / sum(prob)
# get number dispersing and staying
# (if this is not the first cell considered, we use the original population
# to make sure new arrivals don't disperse again)
n_total <- original_pop[origin, stage]
n_dispersing <- n_total * prop_dispersing[stage]
n_dispersing <- round_pop(n_dispersing)
n_staying <- n_total - n_dispersing
# update pop and original_pop to remove the dispersers
new_arrivals <- pop[origin, stage] - n_total
pop[origin, stage] <- n_staying + new_arrivals
# propose some dispersals
dispersals <- n_dispersing * prob
dispersals <- round_pop(dispersals)
# if we're using carrying capacity, return individuals to the origin if there's no space
if (!is.null(carrying_capacity)) {
# get space left in each, and excess dispersers
effective_populations <- pop[destination_ids, , drop = FALSE]
if (length(density_dependence_stages) < total_stages) {
effective_populations[ , -density_dependence_stages] <- 0
# cols <- seq_len(ncol(effective_populations))
# cols <- cols[cols != density_dependence_stages]
# effective_populations[, cols] <- 0
}
effective_population <- rowSums(effective_populations)
space_remaining <- carrying_capacity[destination_ids] - effective_population
# if there's any stochastcity, need to round down
if (steps_stash$demo_stochasticity != "none") {
space_remaining <- floor(space_remaining)
}
excess <- pmax_zero(dispersals - space_remaining) ##### Changed
# remove the excess from the dispersers, and say they are dispersing back to
# the origin
dispersals <- dispersals - excess
pop[origin, stage] <- pop[origin, stage] + sum(excess)
}
# assign them to their population and return
pop[destination_ids, stage] <- pop[destination_ids, stage] + dispersals
pop
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.