##' Utility for taking care of getting/setting/validating parameters.
##'
##' Eventually I'll add a hook here for checking that parameters are
##' allowable, for setting ranges, etc.
##' @title Utility for Parameter Handling
##' @param defaults List of default parameter values. Allowable
##' parameter names are also taken from this list
##' @param where Environment into which parameters will be
##' set/retrieved. If ommited a new environment is generated (so
##' access is only via get/set).
##' @return An object of class \code{parameters}
##' @author Rich FitzJohn
##' @export
make_parameters <- function(defaults, where=new.env()) {
check.names <- function(x) {
names <- names(x)
if (length(x) > 0 &&
(is.null(names) || any(is.na(names)) || any(names == "") ||
any(duplicated(names))))
stop("Names must be present, non-empty and non duplicated")
}
check <- function(parameters) {
check.names(parameters)
if (!all(names(parameters) %in% names))
stop("Unknown parameters: ", setdiff(names(parameters), names))
invisible(TRUE)
}
set <- function(parameters=list()) {
check(parameters)
old <- get()[names(parameters)]
for (v in names(parameters))
assign(v, parameters[[v]], where)
invisible(old)
}
get <- function()
structure(lapply(names, base::get, where), names=names)
check.names(defaults)
names <- names(defaults)
local({
for (.v in names(defaults))
assign(.v, defaults[[.v]], where)
})
rm(defaults)
ret <- list(set=set, get=get, names=function() names)
class(ret) <- "parameters"
ret
}
##' @export
names.parameters <- function(x, ...)
x$names()
##' @export
`names<-.parameters` <- function(x, value)
stop("Cannot set names of a parameters object")
##' Bin x into a grid, where x may have different densities.
##'
##' @title Bin Values
##' @param x Vector of locations
##' @param y Vector of densities/counts along x
##' @param grid Grid over which to bin the values.
##' @return A vector of length \code{length(grid) - 1} being the
##' number of things in each interval.
##' @author Rich FitzJohn
##' @export
to_grid <- function(x, y, grid) {
i <- findInterval(x, grid, rightmost.closed=TRUE)
unname(sapply(split(y, factor(i, seq_len(length(grid)-1))), sum))
}
##' Turn model output into data to be plotted by \code{image}.
##'
##' @title Generate Heat Map of Community
##' @param res List of model states over time
##' @param xr Range of x values (if ommited will be fit to the data)
##' @param nx Number of x points to discretise over
##' @param sample Thin the time axis by this amount (10 means take one
##' point every 10 time steps). This may not be ideal where time does
##' not proceed in even sized steps.
##' @author Rich FitzJohn
##' @export
discretise <- function(res, xr=NULL, nx=101, sample=10) {
t <- sapply(res, "[[", "t")
x <- lapply(res, "[[", "x")
y <- lapply(res, "[[", "y")
if (is.null(xr))
xr <- unlist(range(x))
xx <- seq(xr[1], xr[2], length.out=nx)
j <- seq(1, length(res), by=sample)
mat <- sapply(j, function(i)
to_grid(x[[i]], y[[i]], xx))
mat[mat == 0] <- NA
list(x=xx, t=t[j], y=mat)
}
##' Build a function that steps the system through one
##' generation. Order of events is: (1) resolve fitness (2) introduce
##' mutants.
##'
##' @title Step System Forward in Time
##' @param fitness Function for computing fitness. Must take
##' arguments \code{x_new} (phenotypes to compute fitness for),
##' \code{x} (resident phenotypes) and \code{y} (resident abundances)
##' and return the per-capita growth rate (g). The population will step
##' forward with an Euler step, so that the new \code{y} will be
##' \code{y + y * g * dt}.
##' @param mutation Function for generating mutants. Must take
##' arguments \code{traits} (resident phenotypes) and \code{mutants}
##' (the number of mutants to generate, for each phenotype). See
##' \code{\link{make_mutation}}, which generates a useful function.
##' @param dt Size of the time step
##' @param mu Mutation rate. On average there will be \code{mu*dt*y}
##' mutations, with the actual number drawn from Poisson
##' distribution.
##' @param y_initial Abundance at which to introduce new mutants.
##' @return A Function that moves the system forward in time; it takes
##' a list with elements \code{x} (traits), \code{y} (abundances) and
##' \code{t} (time) and returns a list with these updated to the next
##' steps.
##' @author Rich FitzJohn
##' @export
make_step <- function(fitness, mutation, dt, mu, y_initial) {
function(sys) {
# births/time/individual * individuals * time -> births
dy <- fitness(sys$x, sys$x, sys$y) * dt * sys$y
# mutations/time/individual * individuals * time -> mutation
mutants <- mutation(sys$x, rpois(length(sys$y), mu * dt * sys$y))
sys$y <- sys$y + dy
if (length(mutants) > 0) {
sys$x <- c(sys$x, mutants)
sys$y <- c(sys$y, rep_len(y_initial, length(mutants)))
}
sys$t <- sys$t + dt
sys
}
}
##' @export
##' @rdname make_step
make_step_continuous <- function(fitness, mutation, dt, mu, y_initial,
...) {
step_deterministic <- make_step_deterministic(fitness, ...)
function(sys) {
y0 <- sys$y
sys <- step_deterministic(sys, dt)
# Work out mutational input from the integrated number of
# individuals (quick and dirty) via trapezium rule times the
# mutation rate (per time).
# mutations/time/individual * individuals * time -> mutation
mutants <- mutation(sys$x,
rpois(length(y0), mu * dt * (y0 + sys$y)/2))
if (length(mutants) > 0) {
sys$x <- c(sys$x, mutants)
sys$y <- c(sys$y, rep_len(y_initial, length(mutants)))
}
sys
}
}
##' @export
##' @rdname make_step
##' @importFrom deSolve lsoda
make_step_deterministic <- function(fitness, ...) {
## NOTE: This will actually work with non-scalar dt, which is
## probably not desirable given that we write to sys directly.
##
## NOTE: The time scaling argument here is useful in times where the
## rates of change are very very low so that the system wants to
## equilibrate over millions of time units rather than tens.
function(sys, dt, hini=0, time_scale=1) {
derivs <- function(t, y, ...)
list(fitness(sys$x, sys$x, y) * y * time_scale)
t0 <- sys$t
t1 <- t0 + dt
tmp <- lsoda(sys$y, c(t0, t1), derivs,
hmax=0, maxsteps=10000, hini=hini, ...)
if (tmp[-1,1] < t1)
stop("Did not complete integration")
sys$t <- unname(tmp[-1,1]) * time_scale
sys$y <- unname(tmp[-1,-1])
sys$y[sys$y < 0] <- 0.0
attr(sys, "step_size") <- attr(tmp, "rstate")[[1]] * time_scale
sys
}
}
##' \code{make_step_equilibrium} does a very simple minded attempt to
##' converge on the equilibrium of a system by running it for a long
##' time. It advances the system by \code{dt} (should be a largeish
##' number) a number of times until the population size stabilises.
##' This could be replaced by a multidimensional root finding
##' approach.
##'
##' @export
##' @param method method to find equilibrium. Must be one of
##' "runsteady" or "nleqslv" (or a contraction).
##' @param ... Additional arguments passed through to
##' either \code{rootSolve::runsteady} or \code{nleqslv::nleqslv}.
##' @rdname make_step
make_step_equilibrium <- function(fitness, ..., method) {
switch(match.arg(method, c("runsteady", "nleqslv")),
runsteady= make_step_equilibrium_runsteady,
nleqslv = make_step_equilibrium_nleqslv)(fitness, ...)
}
make_step_equilibrium_naive <- function(fitness, eps, dt=100, max_iter=100,
...) {
step <- make_step_deterministic(fitness, ...)
function(sys) {
.sys <- sys
time_scale <- 1
for (i in seq_len(max_iter)) {
sys$t <- 0
y0 <- sys$y
sys <- step(sys, dt, time_scale=time_scale)
dy <- sys$y - y0
if (all(dy < eps | sys$y < eps))
return(sys)
time_scale <- max(1, attr(sys, "step_size"))
}
stop("Failed to converge on equilibrium (maximum iterations reached)")
}
}
##' @importFrom rootSolve runsteady
make_step_equilibrium_runsteady <- function(fitness, max_time=1e5, ...) {
function(sys) {
derivs <- function(t, y, pars)
list(fitness(sys$x, sys$x, y) * y)
sys$y <- runsteady(sys$y, derivs, times=c(0, max_time), ...)$y
sys$y[sys$y < 0] <- 0
sys
}
}
##' @importFrom nleqslv nleqslv
make_step_equilibrium_nleqslv <- function(fitness, ...) {
function(sys) {
target <- function(y)
fitness(sys$x, sys$x, y) * y
sys$y <- nleqslv(sys$y, target, ...)$x
sys$y[sys$y < 0] <- 0
sys
}
}
## Calculate the rate w_k = mu * \hat n * b(s_k) for the emergence of
## a mutant. We are going to drop b(s_k), partly because it is
## constant for all genotypes and partly because it seems daft to
## invoke separation of timescales while mixing timescales.
##
## In theory, here we should compute the fitness of the individual and
## accept only if runif(1) < f(s') / b(s') but we're skipping this
## step for now.
##' @param step_equilibrium Function generated by
##' \code{make_step_equilibrium}.
##' @export
##' @rdname make_step
make_step_mutation_limited <- function(step_equilibrium,
mutation, mu, y_initial) {
force(step_equilibrium)
function(sys) {
t0 <- sys$t
sys <- step_equilibrium(sys)
## NOTE: Notation from Ito (2007) -- w is not a good choice here.
w <- sys$y * mu
## Update evolutionary time; ignore the ecological time.
sys$t <- t0 + rexp(1, sum(w))
## Create a single mutation:
i <- sample(length(w), 1, prob=w)
mutant <- mutation(sys$x, as.integer(seq_along(w) == i))
sys$x <- c(sys$x, mutant)
sys$y <- c(sys$y, y_initial)
sys
}
}
##' Cleanup phenotypes that have dropped below a threshold abundance
##'
##' @title Cleanup Rare Phenotypes
##' @param eps Abundance below which species are considered extinct.
##' @author Rich FitzJohn
##' @export
make_cleanup <- function(eps) {
function(sys) {
keep <- sys$y >= eps
if (!all(keep)) {
sys$x <- sys$x[keep]
sys$y <- sys$y[keep]
}
sys
}
}
##' Run a system forward in time.
##'
##' @title Run System
##' @param sys a list with elements \code{x} (traits), \code{y} (abundances) and
##' \code{t} (time)
##' @param n_steps The number of steps to run the system for.
##' @param step Step function (see \code{\link{make_step}})
##' @param cleanup Cleanup function (optional - by default do no cleanup)
##' @param print_every Interval at which to print some progress information
##' @author Rich FitzJohn
##' @export
run <- function(sys, n_steps, step, cleanup=identity, print_every=0) {
res <- vector("list", length(n_steps+1))
res[[1]] <- sys
for (i in seq_len(n_steps)) {
if (print_every > 0 && i %% print_every == 0)
message(sprintf("%d: t = %2.2f, n = %d", i, sys$t, length(sys$y)))
sys <- step(sys)
sys <- cleanup(sys)
res[[i+1]] <- sys
}
res
}
##' Helper function for constructing model systems
##' @title Construct Model System
##' @param x Species traits
##' @param y Species densities
##' @param ... Additional system properties (named!)
##' @param t Time (optional)
##' @author Rich FitzJohn
##' @export
sys <- function(x, y, ..., t=0) {
ret <- list(x=x, y=y, ..., t=t)
check_sys(ret)
ret
}
check_sys <- function(sys) {
if (is.vector(sys$x) && length(sys$x) != length(sys$y)) {
stop("Lengths of x and y must be the same")
} else if (is.matrix(sys$x) && ncol(sys$x) != length(sys$y)) {
stop("Number of columns x and length of y must be the same")
}
}
##' Split a community into two parts, naively.
##'
##' This is just a little utility
##' @title Split Community
##' @param sys Community with components 'x' representing species
##' traits. Must be a single value at the moment (i.e.,
##' single-resident, single-traits)
##' @param dx Amount to perturb the community. Either a scalar
##' (traits will be symmetrically moved this amount) or a vector of
##' length 2.
##' @author Rich FitzJohn
##' @export
sys_split <- function(sys, dx) {
if (length(sys$x) != 1) {
stop("Requires single-resident, single-trait community at present")
}
if (length(dx) == 1) {
dx <- c(-1, 1) * dx
} else if (length(dx) != 2) {
stop("dx must be length 1 or 2")
}
sys(sys$x + dx, rep(sys$y/2, 2))
}
# Excuse the slightly odd name: this is designed to be used from
# within functions called 'equilibrium', so we need to be able to
# distinguish it somehow. May tidy that up later...
equilibrium_ <- function(dydt, pars, y, method="runsteady",
init_time=200, max_time=1e5) {
method <- match.arg(method, c("runsteady", "nleqslv", "simple"))
if (init_time > 0) {
y <- unname(lsoda_nolist(y, c(0, init_time), dydt, pars)[2,-1])
}
if (method == "runsteady") {
dydt.deSolve <- function(...) list(dydt(...))
ans <- runsteady(y, dydt.deSolve, pars, times=c(0, Inf), hmax=1)$y
} else if (method == "nleqslv") {
ans <- nleqslv(y, function(y) dydt(0, y, pars), global="none")$x
} else if (method == "simple") {
ans <- unname(lsoda_nolist(y, c(init_time, max_time), dydt, pars)[2,-1])
}
ans
}
# For models that have the sys/fitness set up.
equilibrium_sys <- function(sys, fitness, method="runsteady",
init_time=200, max_time=1e5) {
derivs <- function(t, y, x) {
fitness(x, x, y) * y
}
y <- equilibrium_(derivs, sys$x, sys$y, method, init_time, max_time)
sys(sys$x, y)
}
lsoda_nolist <- function(y, times, func, ...) {
lsoda(y, times, function(...) list(func(...)), ...)
}
colMins <- function(x) {
apply(x, 2, min)
}
rowMins <- function(x) {
apply(x, 1, min)
}
quadratic_roots <- function(a, b, c) {
(-b + c(-1, 1) * sqrt(b*b - 4*a*c))/(2 * a)
}
get_attribute <- function(x, which) {
ret <- attr(x, which, TRUE)
if (is.null(ret)) {
stop(sprintf("attribute '%s' not found in '%s'",
which, deparse(substitute(x))))
}
ret
}
##' Variant of \code{abline} that puts a line through a point (x,y)
##'
##' Like \code{abline}, this does not support recycling (uses
##' \code{abline} internally).
##'
##' @title Abline Through Point
##' @param x X coordinate of point to pass through
##' @param y Y coordinate of point to pass through
##' @param m Slope of line
##' @param ... Arguments passed to \code{abline} (e.g., col)
##' @author Rich FitzJohn
##' @export
abcline <- function(x, y, m, ...) {
abline(y - x * m, m, ...)
}
##' Make colours semi-transparent
##'
##' @title Semi Transparent Colours
##' @param col Vector of colours
##' @param opacity Vector of scalar of opacity
##' @author Rich FitzJohn
##' @export
make_transparent <- function(col, opacity=.5) {
alpha <- opacity
if ( length(alpha) > 1 && any(is.na(alpha)) ) {
n <- max(length(col), length(alpha))
alpha <- rep(alpha, length.out=n)
col <- rep(col, length.out=n)
ok <- !is.na(alpha)
ret <- rep(NA, length(col))
ret[ok] <- make_transparent(col[ok], alpha[ok])
ret
} else {
tmp <- col2rgb(col)/255
rgb(tmp[1,], tmp[2,], tmp[3,], alpha=alpha)
}
}
##' Mix two colours together
##'
##' @title Mix Two Colours together
##' @param col1 Vector of colours
##' @param col2 Vector of colours
##' @param p AMount of colour vector 2 to take
##' @author Rich FitzJohn
##' @export
mix <- function(col1, col2, p=0.5) {
m <- col2rgb(col1, alpha=TRUE)
m2 <- col2rgb(rep(col2, length=length(col1)), alpha=TRUE)
m3 <- (m * p + m2 * (1-p))/255
alpha <- if (all(m3[4,] == 1)) NULL else m3[4,]
rgb(m3[1,], m3[2,], m3[3,], alpha)
}
assert_scalar <- function(x, name=deparse(substitute(x))) {
if (length(x) != 1) {
stop(sprintf("%s must be a scalar", name), call.=FALSE)
}
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.