TLDR: My main challenge is just how do I write the root function that checks an arbitrary number of state variables, x, and then apply the event function such that all state variables n that have a value less than the threshold (n <= x) are acted upon by the event function?
I'm trying to use deSolve for a set of Lotka-Volterra equations, but with many state variables (i.e. not just a predator and prey but 20 interacting organisms).
I want to use a root function and event function to be constantly checking if any state variable values dip below a threshold value (e.g. 1.0) and if they do, use the event function to make that particular state variable 0. I've been messing around with a simple minimal example, but can't quite understand how to extend this to check all the state variables and just apply to the one(s) that is/are below the threshold.
The LV example from the deSolve package vignette
LVmod <- function(Time, State, Pars) {
with(as.list(c(State, Pars)), {
Ingestion <- rIng * Prey * Predator
GrowthPrey <- rGrow * Prey * (1 - Prey/K)
MortPredator <- rMort * Predator
dPrey <- GrowthPrey - Ingestion
dPredator <- Ingestion * assEff - MortPredator
return(list(c(dPrey, dPredator)))
})
}
pars <- c(rIng = 0.2, # /day, rate of ingestion
rGrow = 1.0, # /day, growth rate of prey
rMort = 0.2 , # /day, mortality rate of predator
assEff = 0.5, # -, assimilation efficiency
K = 10) # mmol/m3, carrying capacity
yini <- c(Prey = 10, Predator = 2)
times <- seq(0, 50, by = 1)
I can apply my root and event functions to check for just the prey's values:
## event triggered if state variable less than 1
rootfun <- function (Time, State, Pars) {
return(State[1] - 1)
}
## sets state variable = 1
eventfun <- function(Time, State, Pars) {
return(c(State[1] <- 0, State[2]))
}
out <- lsode(yini, times, LVmod, pars,
rootfunc = rootfun,
events = list(func = eventfun, root = TRUE))
## User specified plotting
matplot(out[ , 1], out[ , 2:3], type = "l", xlab = "time", ylab = "Conc",
main = "Lotka-Volterra", lwd = 2)
legend("topright", c("prey", "predator"), col = 1:2, lty = 1:2)
And the result is this:
But now I want to extend this so that it checks all the state variables (in this case just the 2), but ideally in a way that is flexible to different numbers of state variables. I have tried messing around with doing this in some sort of loop but can't seem to figure it out. My main challenge is just how do I write the root function that checks an arbitrary number of state variables, x, and then apply the event function such that all state variables n that have a value less than the threshold (n <= x) are acted upon by the event function?
Perhaps useful information is at some point I would like to implement a separate (not root-based) event function to change a parameter at some pre-set times, so ideally the solution to this problem could interface with additional event function implementation.
Help much appreciated as always!!
One can use a vectorized version of the LV model and then write rootfun and eventfun also in vectorized style:
library(deSolve)
model <- function(t, y, parms) {
with(parms, {
dy <- r * y + y * (A %*% y)
list(dy)
})
}
## int6eraction matrix
parms <- list(
r = c(r1 = 0.1, r2 = 0.1, r3 = -0.1, r4 = -0.1),
A = matrix(c(
0.0, 0.0, -0.2, 0.0, # prey 1
0.0, 0.0, 0.0, -0.1, # prey 2
0.2, 0.0, 0.0, 0.0, # predator 1; eats prey 1
0.0, 0.1, 0.0, 0.0), # predator 2; eats prey 2
nrow = 4, ncol = 4, byrow = TRUE)
)
times = seq(0, 150, 1)
y0 = c(n1 = 1, n2 = 1, n3 = 2, n4 = 2)
out <- ode(y0, times, model, parms)
plot(out)
## defined as global variables for simplicity, can also be put into parms
threshold <- 0.2 # can be a vector of length(y0)
y_new <- 1.0 # can be a vector of length(y0)
## uncomment the 'cat' lines to see what's going on
rootfun <- function (t, y, p) {
#cat("root at t=", t, "\n")
#cat("y old =", y, "\n")
return(y - threshold)
}
eventfun <- function(t, y, p) {
#cat("y old =", y, "\n")
y <- ifelse(y <= threshold, y_new, y)
#cat("y new =", y, "\n")
return(y)
}
out <- ode(y0, times, model, parms,
events = list(func = eventfun, root = TRUE), rootfunc=rootfun)
plot(out)
Related
I am trying to solve this system of ODEs through deSolve, dX/dt = -X*a + (Y-X)b + c and dY/dt = -Ya + (X-Y)*b for time [0,200], a=0.30, b=0.2 but c is 1 for time [50,70] and 0 otherwise. The code I have been using is,
time <- seq(0, 200, by=1)
parameters <- c(a=0.33, b=0.2, c=1)
state <- c(X = 0, Y = 0)
two_comp <- function(time, state, parameters){
with(as.list(c(state, parameters)), {
dX = -X*a + (Y-X)*b + c
dY = -Y*a + (X-Y)*b
return(list(c(dX, dY)))
})
}
out <- ode(y = state, times = time, func = two_comp, parms = parameters)
out.df = as.data.frame(out)
I have left out the time varying part of the c parameter since I can't figure out a way to include it and run it smoothly. I tried including it in the function definitions, but to no avail.
The standard way is to use approxfun, i.e. create a time dependent signal, that we also call forcing variable:
library("deSolve")
time <- seq(0, 200, by=1)
parameters <- c(a=0.33, b=0.2, c=1)
state <- c(X = 0, Y = 0)
two_comp <- function(time, state, parameters, signal){
cc <- signal(time)
with(as.list(c(state, parameters)), {
dX <- -X * a + (Y - X) * b + cc
dY <- -Y * a + (X - Y) * b
return(list(c(dX, dY), c = cc))
})
}
signal <- approxfun(x = c(0, 50, 70, 200),
y = c(0, 1, 0, 0),
method = "constant", rule = 2)
out <- ode(y = state, times = time, func = two_comp,
parms = parameters, signal = signal)
plot(out)
Note also the deSolve specific plot function and that the time dependent variable cc is used as an additional output variable.
More about this can be found:
in the ?forcings help page and
in a short tutorial on Github.
The interval limits where c is equal to 1 can be passed as parameters. Then, inside the differential function, use them to create a logical value
time >= lower & time <= upper
Since FALSE/TRUE are coded as the integers 0/1, every time this condition is false, c is multiplied by zero and the trick is done.
library(deSolve)
two_comp <- function(time, state, parameters){
with(as.list(c(state, parameters)), {
dX = -X*a + (Y-X)*b + c*(time >= lower & time <= upper)
dY = -Y*a + (X-Y)*b
return(list(c(dX, dY)))
})
}
time <- seq(0, 200, by=1)
parameters <- c(a=0.33, b=0.2, c=1, lower = 50, upper = 70)
state <- c(X = 0, Y = 0)
out <- ode(
y = state,
times = time,
func = two_comp,
parms = parameters
)
out.df <- as.data.frame(out)
head(out.df)
matplot(out.df$time, out.df[-1], type = "l", lty = "solid", ylim = c(0, 3))
legend("topright", legend = names(out.df)[-1], col = 1:2, lty = "solid")
I am trying to model a disturbance event in a generalized Lotka-Volterra model, where at time t, 1 is added to the variable e. I keep on running into the following error:
Error in checkevents(events, times, Ynames, dllname) :
unknown state variable in 'event': e
My model is the following:
lvg<-function(t, N, e, param){
e <- 0
dNdt <- N * r + N * (a %*% N) - N * e
list(c(dNdt))
}
where N is the population size of species i, r is the growth rate, a is the interaction matrix, and e is the event. r and a are specified as prior parameters, the event is specified in a dataframe. A simplified version is as follows:
#set parameters
S<- 10 # number of species
r <- rep(1.1, S) # growth rates
a <- matrix (nrow = S, ncol = S) #interaction matrix
a[lower.tri(a)] <- -0.001
a[upper.tri(a)] <- -0.001
diag(a) <- -0.01
parms <- list (r, a) #put parameters in a list
N0 <- rep(100, S) #initial values for species abundances
ts<-seq(0, 100, 1) # time steps for solver
#create data frame for event
eventdat <- data.frame(var = c("e", "e"), time = c(10, 20), value = c(1, 1), method = c("add"))
lvout<-lsoda(N0, ts, lvg, parms, events = list(data = eventdat))
Here an approach with an event function instead of an event table, that is in general more flexible in the case here simpler. Note also that the number of states and values of parameters were changed to get a more typical L&V model:
library(deSolve)
## multi-species Lotka-Volterra
lvg <- function(t, N, param) {
with(param, {
dNdt <- r * N + N * (a %*% N)
list(c(dNdt))
})
}
## simplified to 4 species, you can add more
S <- 4
N0 <- c(1,1,1,1)
## parameter list
parms <- list(
r = c(r1 = 0.5, r2 = 0.5, r3 = -0.5, r4 = -0.5),
a = matrix(c(
0.0, 0.0, -0.5, 0.0, # prey 1
0.0, 0.0, 0.0, -0.2, # prey 2
0.5, 0.0, 0.0, 0.0, # predator 1; eats prey 1
0.0, 0.2, 0.0, 0.0), # predator 2; eats prey 2
nrow = 4, ncol = 4, byrow = TRUE),
e = rep(0.5, S)
)
ts <- seq(0, 100, 1) # time steps for solver
te <- c(20, 40) # event times
## event function is more flexible than an event table
eventfun <- function(t, N, param){
with (as.list(param), {
N <- N - N * e
return(c(N))
})
}
## simulation without events
lvout<-lsoda(N0, ts, lvg, parms)
plot(lvout)
## simulation with events
lvout<-lsoda(N0, ts, lvg, parms, events = list(func = eventfun, time = te))
plot(lvout)
I have a system with X connected patches, each has a simple predator-prey model like that:
C_i = r_c*C -d*C + e*P + b*\sum_j(A_ij*C_j)
P_i = r_p*P +e*P + b*\sum_j(A_ij*P_j)
where C_i and P_i are consumer and prey abundances in patch i; r_c,r_p are growth rates; d is rate of consumer death, e is rate of prey intake. The last term in each equation is the influx of consumers or prey: A_ij is a matrix indicating if patches i and j are connected, and b is a rate of migration from patch j. (My system is more complicated but this simple example will do).
This kind of system will require as many ODE systems as there are patches. Any idea how to implement this kind of system? I know how to implement it for a single patch (no indices and no influx term) with deSolve. So any solution with deSolve is preferred.
Latex version of equations:
Consumer equation in patch i
Prey equation in patch i
The following matrix predator-prey model may serve as a starting point:
library(deSolve)
model <- function(t, n, parms) {
with(parms, {
dn <- r * n + n * (A %*% n)
list(dn)
})
}
parms <- list(
r = c(r1 = 0.1, r2 = 0.1, r3 = -0.1, r4 = -0.1),
A = matrix(c(
0.0, 0.0, -0.2, 0.0, # prey 1
0.0, 0.0, 0.0, -0.1, # prey 2
0.2, 0.0, 0.0, 0.0, # predator 1; eats prey 1
0.0, 0.1, 0.0, 0.0), # predator 2; eats prey 2
nrow = 4, ncol = 4, byrow = TRUE)
)
times = seq(0, 500, 0.1)
n0 = c(n1 = 1, n2 = 1, n3 = 2, n4 = 2)
out <- ode(n0, times, model, parms)
plot(out)
Here a more meta-population related example following our off-list discussion. Please not that this is just experimental and comes WITHOUT WARRANTY. Feedback and improvements are welcome.
library(deSolve)
n <- 7 # number of metapopulations
beta <- rep(c(-500, 500, 0), each = n)
gamma <- rep(c(0, -365/13, 365/13), each = n)
## case(1) a "fully connected" system
#mig <- 1e-10 # migration rate
#As <- matrix(mig, nrow=n, ncol=n)
#diag(As) <- 0
## case (2) directed move
mig <- 0.0001 # migration rate
As <- matrix(0, nrow=n, ncol=n)
As[1:(n-1), 2:n] <- diag(mig, n-1)
As[2:n, 1:(n-1)] <- As[2:n, 1:(n-1)] + diag(mig, n-1)
## case (3) enter migration matrix manually ...
## expand movement to full matrix, within respective states S, I, R
## assumes that all states move equally; this can of course be changed
A <- matrix(0, nrow = 3 * n, ncol = 3 * n)
A[1:n, 1:n] <- As
A[(n+1):(2*n), (n+1):(2*n)] <- As
A[(2*n+1):(3*n), (2*n+1):(3*n)] <- As
## balance: what moves to other cells needs to be removed from the cell itself
diag(A) <- -rowSums(A)
## migration matrix A
## - positive values: what moves from the neighbors
## - negative values: what moves to the neighbors
A
S <- rep(0.99, times=n)
I <- c(0.01, rep(0, n-1)) # only first sub-population infected
R <- rep(0, times=n)
Y0 <- c(S, I, R)
sirmodel <- function(t, Y, parameters) {
S <- Y[1:n]
I <- Y[(n+1):(2*n)]
# dS <- -beta*S*I
# dI <- beta*S*I-gamma*I
# dR <- gamma*I
dY <- beta * S * I + gamma * I + Y %*% A
list(dY)
}
times <-seq(from=0, to=0.2, length.out=100)
out <- ode(y = Y0, times = times, func = sirmodel, parms = NULL)
windows(height = 6, width = 2 * n) # create somewhat bigger window
plot(out, xlab = "time", ylab = "-", mfrow=c(3, n))
I have taken this example from documentation for demonstration purposes. In the following example when the value of y reaches to 0.1 a random value is added. I want to terminate the solver if the y value is greater than 0.8.
One possible solution is to generate a random value in eventfun such that y is always less than 0.8.
Is there any other possible solution to terminate the solver? This would be helpful in my complicated model.
## =======================================================================
## Example 3:
## using lsodar to trigger an event
## =======================================================================
## a state variable is decaying at a first-order rate.
## when it reaches the value 0.1, a random amount is added.
library("deSolve")
derivfun <- function (t,y,parms)
list (-0.05 * y)
rootfun <- function (t,y,parms)
return(y - 0.1)
eventfun <- function(t,y,parms)
return(y + runif(1))
yini <- 0.5
times <- 0:400
out <- lsodar(func=derivfun, y = yini, times=times,
rootfunc = rootfun, events = list(func=eventfun, root = TRUE))
plot(out, type = "l", lwd = 2, main = "lsodar with event")
# }
Does the following what you want? Thanks for the clear example.
library("deSolve")
derivfun <- function (t,y,parms)
list (-0.05 * y)
rootfun <- function (t,y,parms)
return(c(y - 0.1, y - 0.8))
eventfun <- function(t,y,parms)
return(y + runif(1))
yini <- 0.5
times <- 0:400
out <- lsodar(func=derivfun, y = yini, times=times,
rootfunc = rootfun,
events = list(func=eventfun, root = TRUE, terminalroot = 2))
plot(out, type = "l", lwd = 2, main = "lsodar with event")
And here another refinement of rootfun and eventfun:
library("deSolve")
terminate <- 0.8
eps <- 1e-6
derivfun <- function (t, y, parms)
list (-0.05 * y)
rootfun <- function (t, y, parms)
return(c(y - 0.1, y - terminate))
eventfun <- function(t, y, parms)
return(min(y + runif(1), terminate + eps))
yini <- 0.5
times <- 0:400
out <- lsodar(func=derivfun, y = yini, times=times,
rootfunc = rootfun,
events = list(func = eventfun, root = TRUE, terminalroot = 2))
plot(out, type = "l", lwd = 2, main = "lsodar with event")
I'm using the FME package to fit a prey-predator model to my data. At the moment I'm just learning and testing the code without fitting anything yet, following this example: http://strimas.com/r/lotka-volterra/
When executing the global sensitivity analyses I get an error and I can't figure out where it comes from.
When I copy paste the example above in my R session, it works. But I can't get my own code to work.
I wrote my function based on another document on FME so the syntax is slightly different.
# model function
RMmodel <- function(t, y, parms) {
#y <- c(N = 30, P = 3)
derivs <- function(t, y, parms) {
with(as.list(c(y, parms)), {
dN <- r * N * (1 - N/K) - a * N * P / (1 + a*h*N)
dP <- e * a * N * P / (1 + a*h*N) - m * P
return(list(c(dN, dP)))
})
}
return(ode(y = y, times = t, parms = parms, func = derivs))
}
# input
parameters <- c(r = 0.4, # growth rate prey
K = 2200, # carrying capacity prey
a = 0.14, # search rate predator
h = 1, # handling time
e = 1.2, # assimilation efficiency predator
m = 0.2) # mortality rate predator
init <- c(N = 30, P = 3)
times <- seq(0, 120, by=1)
# calculate ODE
RM_result <- RMmodel(t = times, y = init, parms = parameters) # this works
# global sensitivity
par_ranges <- data.frame(min = c(0.18, 1500, 0.01, 0.01, 0.01, 0.01),
max = c(0.25, 3500, 2, 2, 2, 2),
row.names = c("r", "K", "a", "h", "e", "m"))
RM_glob_sens <- sensRange(func = RMmodel, parms = parameters,
dist = "grid",
sensvar = c("N", "P"), parRange = par_ranges,
num = 20, t = times)
When I run the above code I get the following error:
Error in ode(y = y, times = t, parms = parms, func = derivs) :
argument "parms" is missing, with no default
I tried the following things: adjusted the argument name in the model to "pars" instead of "parms"; added the initial state within the model function; changed the object name of "parameters" to "pars"; changed the order within sensRange(); put the parameters directly in sensRange through c(a = , ...).
I am clearly missing someting but I can't find it for the life of me.
Anyone a suggestion?
A requirement of sensRange() is that the first argument of func is parms (see documentation). Change the order of the input arguments in RMmodel and derivs so that parms is the first argument and your code will work.