a deSolve pitfall

Beware!

skullcross

As suggested in the ode() examples, I typically use a with() statement in my gradient function for convenience, so I don't have to refer to the order of the parameters in the parameter vector or the order of the variables in the state vector. I thought this meant that I didn't have to worry about ordering at all, but that's not true. Here's an example/explanation.

Set up a simple linear model (\( x \) exponentially increasing, \( y \) exponentially decreasing):

library("deSolve")
gradfun <- function(t, statevec, params) {
    g <- with(as.list(c(statevec, params)), c(x = a * x, y = b * y))
    list(g)
}
params0 <- c(a = log(2), b = -log(2))
tvec <- 0:5

Starting at x=0, y=1, the right way:

ode(c(x = 0, y = 1), tvec, gradfun, params0)
##   time x       y
## 1    0 0 1.00000
## 2    1 0 0.50000
## 3    2 0 0.25000
## 4    3 0 0.12500
## 5    4 0 0.06250
## 6    5 0 0.03125

As should happen, \( x \) remains at zero and \( y \) decreases exponentially back toward zero.

What happens if we get the order of the initial conditions wrong (even though the names are right?)

ode(c(y = 1, x = 0), tvec, gradfun, params0)
##   time       y       x
## 1    0  1.0000  0.0000
## 2    1  0.7692 -0.6390
## 3    2  0.1835 -0.9830
## 4    3 -0.4870 -0.8734
## 5    4 -0.9327 -0.3607
## 6    5 -0.9479  0.3185

The system is now cross-wired, equivalent to

\[ \begin{split} \dot x & = -y \\ \dot y & = x \end{split} \]

cbind(cos(log(2) * tvec), -sin(log(2) * tvec))
##         [,1]    [,2]
## [1,]  1.0000  0.0000
## [2,]  0.7692 -0.6390
## [3,]  0.1835 -0.9830
## [4,] -0.4870 -0.8734
## [5,] -0.9327 -0.3607
## [6,] -0.9479  0.3185

What is happening here?

Hacked solution

This is a workable solution, I think: it depends on the user having assigned appropriate names to the gradient vector (this is good practice in any case).

myCheckFunc <- deparse(deSolve:::checkFunc)
myCheckFunc <- c(myCheckFunc[1:3], "   if (!all(names(tmp[[1]])==names(y))) {", 
    "warning('name mismatch between y and results of grad()')", " }", myCheckFunc[4:length(myCheckFunc)])
myCheckFunc <- eval(parse(text = myCheckFunc), envir = environment(deSolve:::checkFunc))
assignInNamespace("checkFunc", myCheckFunc, ns = "deSolve")
ode(c(y = 1, x = 0), tvec, gradfun, params0)
## Warning: name mismatch between y and results of grad()
##   time       y       x
## 1    0  1.0000  0.0000
## 2    1  0.7692 -0.6390
## 3    2  0.1835 -0.9830
## 4    3 -0.4870 -0.8734
## 5    4 -0.9327 -0.3607
## 6    5 -0.9479  0.3185

No warning if the gradient vector is not named (it would be possible to add this check, i.e. warn if y has names but the result of grad() doesn't).

gradfun2 <- function(t, statevec, params) {
    g <- with(as.list(c(statevec, params)), c(a * x, b * y))
    list(g)
}
ode(c(y = 1, x = 0), tvec, gradfun2, params0)
##   time       y       x
## 1    0  1.0000  0.0000
## 2    1  0.7692 -0.6390
## 3    2  0.1835 -0.9830
## 4    3 -0.4870 -0.8734
## 5    4 -0.9327 -0.3607
## 6    5 -0.9479  0.3185