8

I'm currently working on solving a system of ordinary differential equations using deSolve, and was wondering if there's any way of preventing differential variable values from going below zero. I've seen a few other posts about setting negative values to zero in a vector, data frame, etc., but since this is a biological model (and it doesn't make sense for a T cell count to go negative), I need to stop it from happening to begin with so these values don't skew the results, not just replace the negatives in the final output.

Ben Bolker
  • 211,554
  • 25
  • 370
  • 453
Dorian
  • 83
  • 5
  • Are you looking for parameter constraints? – Roman Luštrik Jan 14 '17 at 11:23
  • That may help-- my main problem isn't with the parameters but with the independent variables. Is there any equivalent of, e.g., MATLAB's nonnegative function, or code I can implement so that x[1] can never go below 0? – Dorian Jan 14 '17 at 18:12
  • What happens in the equations when x[1] hits zero? Is zero a stable fixed point? If so you can set the first negative value and everything later than it to zero. Also in the function defining the ODE you can set `x[1]=max(x[1],0)` then the dynamics should be correct even if it requires post processing. – goryh Nov 14 '19 at 21:35

2 Answers2

10

My standard approach is to transform the state variables to an unconstrained scale. The most obvious/standard way to do this for positive variables is to write down equations for the dynamics of log(x) rather than of x.

For example, with the Susceptible-Infected-Recovered (SIR) model for infectious disease epidemics, where the equations are dS/dt = -beta*S*I; dI/dt = beta*S*I-gamma*I; dR/dt = gamma*I we would naively write the gradient function as

gfun <- function(time, y, params) {
   g <- with(as.list(c(y,params)),
       c(-beta*S*I,
          beta*S*I-gamma*I,
          gamma*I)
       )
   return(list(g))
}

If we make log(I) rather than I be the state variable (in principle we could do this with S as well, but in practice S is much less likely to approach the boundary), then we have d(log(I))/dt = (dI/dt)/I = beta*S-gamma; the rest of the equations need to use exp(logI) to refer to I. So:

gfun_log <- function(time, y, params) {
   g <- with(as.list(c(y,params)),
       c(-beta*S*exp(logI),
          beta*S-gamma,
          gamma*exp(logI))
       )
   return(list(g))
}

(it would be slightly more efficient to compute exp(logI) once and store/re-use it rather than computing it twice ...)

Ben Bolker
  • 211,554
  • 25
  • 370
  • 453
  • sorry to revive this ancient post but I love your strategy of log-transforming the state variables (and their derivatives) to ensure they remain positive. Do you think log-transform would also help when your variables and time array span a huge dynamic range such as in astrophysics where say a state variable is expected to evolve from ~0 to ~1e10 over billions of years with rapid evolution in the first ~billion years (causing ODE solvers to take prohibitively small timesteps early on)? Or is setting the absolute/relative error tolerance the way to go? – quantumflash Mar 22 '22 at 13:55
  • 1
    interesting question, seems like it might help but don't really know - is there a simple enough example to make into an SO question? – Ben Bolker Mar 22 '22 at 14:01
  • It should be noted that this won’t save you one bit if your model allows for the derivative of a dynamical variable to be negative if that dynamical variable is zero. In that case, the integration will just fail, you get negative overflows, or invalid argument of the logarithm (whatever happens first). – Wrzlprmft Apr 04 '22 at 07:04
0

If a value doesn’t become negative in reality but becomes negative in your model, you should change your model or, equivalently, modify your differential equations such that this is not possible. With other words: Do not try to constrain your dynamical variables but their derivatives. Everything else will only lead to problems with your solver, while it should not care about a change in the differential equation.

For a simple example, suppose that:

  • you have a one-dimensional differential equation ẏ = f(y),
  • y shall not become negative,
  • your initial y is positive.

In this case, y can only become negative if f(0) < 0. Thus, all you have to do is to modify f such that f(0) ≥ 0 (and it is still smooth).

For a proof of principle, you can multiply f with an appropriately modified sigmoid function (which allows you to compose every logical operation with smooth functions). This way, nothing would change for most values of y, and you only change your differential equation if y is close to 0, i.e., when you were going to manipulate things anyway.

However, I would not really recommend using sigmoids without thinking about your model. If your model is totally wrong near y = 0, it will very likely already be useless for nearby values. If your simulations venture in this terrain and you want the results to be meaningful, you should fix this.

Wrzlprmft
  • 4,234
  • 1
  • 28
  • 54
  • Unfortunately I don't think this is possible, as the variables interact with each other. For example, the equation for viral load derived from a certain cell type is dVB <- (k_b * x[7]) - (d_vb * x[1]), where k_b = rate of virus release, x[7] = infected cells, d_vb = rate of virus death and x[1] = viral load. It's possible I'm being ignorant, but I can't see a way to rewrite equations like these to avoid going below 0 after a certain point (in this case, when lytically infected cells run out), which is why I hoped there might be constraints I could put in place to prevent that. – Dorian Jan 14 '17 at 18:18
  • @Dorian: You can definitely implement such constraints, even for interacting variables: Sigmoids allow you to implement all logical operations. However, there very likely is a more plausible way if you improve my model. I strongly recommend to consider this, because if your model is bogus at 0, it almost certainly is bogus near 0 and thus your results may very well be useless. Also see my edit. – Wrzlprmft Jan 14 '17 at 19:00
  • @Dorian: Also, if I understand your model correctly, dVB is the temporal derivative of x[1] (i.e., ẋ[1]), and k_b, x[7], and d_vb are always positive. Therefore, your equations already ensure that x[1] never gets negative, because for x[1]=0, you automatically have a non-negative dVB=ẋ[1]. Therefore, your model already avoids the problem in question – by being a good model. – Wrzlprmft Jan 14 '17 at 19:05
  • @Wrzlprmft: that's true mathematically but not necessarily in a numerical context. – Ben Bolker Jan 14 '17 at 19:05
  • 1
    @BenBolker: Yes and no. For instance in the asker’s example, slightly negative values would be “pushed back” to 0, so such a numerical error is no problem. The same should hold for most models (and if not, you can again modify the differential equation to address this). Moreover, with a good integrator and reasonably chosen parameters, you can avoid this entirely. The asker’s example would never go below 0 with a Runge–Kutta integrator with adaptive step size, where the relative error is controlled. – Wrzlprmft Jan 14 '17 at 19:33