3

I am trying to to solve ODEs restricted to positive solutions, i.e.:

dx/dt=f(x)

with x>=0.

In MATLAB this is very easy to implement. Is there any workaround or package for R to restrict the solution-space to positive values only?

This is very crucial in my case and unfortunately there is no alternative. I searched for a while now but without any success. :-(

Ben Bolker
  • 211,554
  • 25
  • 370
  • 453
Jens
  • 41
  • 3
  • can you say how (technically) it's done in MATLAB? The easiest way is to change your system to d(log(x))/dt = f(log(x)). – Ben Bolker Nov 20 '17 at 21:56
  • Without any sample code and sample data it's difficult to be more specific. So in answer to your question, yes it's possible. Take a look at R package `deSolve`, where you can `subset` variables that meet certain constraints. – Maurits Evers Nov 20 '17 at 21:59
  • @MauritsEvers, I'm not sure it's that easy, but I agree that more detail is necessary. – Ben Bolker Nov 20 '17 at 22:05
  • It is hard to explain but here is the original publication used for MATLAB's ode45 http://www.radford.edu/~thompson/RP/nonnegative.pdf. R's deSolve also uses similar codes but it is not able to enforce non-negativity. I simply want to solve the system dx/dt=f(x) with x>=0 for all x. Let's say (just one example) I have a biological system f(x,p) with measured concentrations y and parameters p. Obviously the stats of the system and the observations have to be positive or equal to zero. Now I want to infer the parameters BUT the y and y have to stay positive. – Jens Nov 20 '17 at 22:06
  • I mean x and y have to stay positive. I know this is a very general question but I really need help. Since I have a given network structure it is hard to just log-transform the system. I searched now half a year for a solution... but without any success ... unfortunately... – Jens Nov 20 '17 at 22:14
  • can you post some code so we can play with it in R and see? – Matt W. Nov 20 '17 at 22:27
  • @BenBolker: Yes, it *can* be that easy. But I agree, specifics will depend on the actual problem. See my simple example below. – Maurits Evers Nov 20 '17 at 22:34
  • Possible duplicate of [Replacing negative values in a model (system of ODEs) with zero](https://stackoverflow.com/questions/41648878/replacing-negative-values-in-a-model-system-of-odes-with-zero) – Wrzlprmft Nov 21 '17 at 10:01
  • FYI: for the steady-state case, `stode` and `stodes` have a parameter `positive` that can be set to `TRUE` to ensure that the solution is positive. – Dan Nov 24 '17 at 18:10
  • Does your differential equation permit negative values (given your initial conditions) or are you afraid of numeric noise driving a value that should be 0 negative and then the dynamics do things you do not want? – goryh Nov 14 '19 at 18:53

4 Answers4

3

There's still not quite enough to go on here. For the sorts of problems I'm familiar with, modifying the system to operate on the scale of the log-transformed state variables works well (you can always back-transform the results e.g. to compare them with data). I have used this, for example, with the SIR model in epidemiology. I'm going to try with @MauritsEver's example, to illustrate transforming the system to operate on the log scale:

library(deSolve)
model <- function (time, y, parms) {
   with(as.list(c(y, parms)), {
       dlogN <-   r * (1 - exp(logN) / K)
       list(dlogN)
   })
}

# Starting conditions
y <- c(logN = log(0.1))
parms <- c(r = 0.1, K = 10)
times <- seq(0, 100, 1)
out <- as.data.frame(ode(y, times, model, parms))
out_backtran <- transform(out,N=exp(logN))
plot(N~time,data=out_backtran)

enter image description here

This approach has the following disadvantages:

  • it won't handle solutions that are exactly on the boundary, and will have trouble with solutions that approach the boundary "too fast" (i.e. where state variables converge to zero in finite time)
  • as written it requires manual translation. It would be entirely possible to write a system that allowed the user to enter a set of equations and a set of transformations and applied the transformations automatically, but it would be some effort.
  • It may increase computational effort slightly (any time we have to use the original-scale value of the state variable we have to exponentiate)
Ben Bolker
  • 211,554
  • 25
  • 370
  • 453
  • Just for my understanding... did you miss the N in r * N * (1 - N / K)? I want to avoid this issue but of course this would force the variables to stay positive. I really can not understand why there is a ocatve, MATLAB and MAPLE is able to force non-negativity in ODE systems but R is not... – Jens Nov 21 '17 at 22:59
  • no, I didn't miss it. It disappears because d(log(N))/dt = (dN/dt)/N ... – Ben Bolker Nov 21 '17 at 23:11
1

Without any specific example code or details on the ODE it's difficult to be more specific. It could be quite simple, depending on the problem.

Here is a trivial example using deSolve and its function deSolve::subset.

# Example straight from the deSolve manual
library(deSolve);
model <- function (time, y, parms) {
    with(as.list(c(y, parms)), {
        dN <-   r * N * (1 - N / K);
        list(dN)
    })
}

# Starting conditions
y <- c(N = 0.1);
parms <- c(r = 0.1, K = 10);
times <- seq(0, 100, 1);

# Solve ODE and plot
out <- ode(y, times, model, parms);
plot(out, type = "l", xlim = c(0, 100));

enter image description here

We now impose a constraint on time and subset the solution.

# Constrain: time > 20 and plot
out.constrained <- subset(out, select = c("time", "N"), subset = time > 20);
plot(out.constrained, type = "l", xlim = c(0, 100));

enter image description here

Maurits Evers
  • 49,617
  • 4
  • 47
  • 68
  • I see. But unfortunately the idea is not to just remove all x values matching to y values below zero. For instance the function y=sqrt(x) can be solved for y>0 and y<0. The same principle you can apply to ODE systems here http://www.radford.edu/~thompson/vodef90web/vodef90source/misc.html has this functionality. I also argued with the authors of odeSolve for a while. But without success... I only want to solve the constrained ODE system restricted to x>0. I am really not sure how I could build a meaningful example because this constraint can basically be applied to any ODE system. – Jens Nov 20 '17 at 22:47
  • I'm not following you. Are you talking about a *constrained system of coupled ODEs*? In the case of a single ODE, imposing a constraint `x>=0` is simply constraining the image of `x(t)`, is it not? – Maurits Evers Nov 20 '17 at 23:02
  • The problem is where the exact solution is positive but converges to zero. Numerical solvers, especially with explicit methods, tend to cross the zero line. As suggested in the comments, a substitution `x=exp(u)` can be used to enforce positivity. – Lutz Lehmann Dec 14 '17 at 00:06
0

I think I have the same problem and might provide you an example.

Consider a dissolution process where a product A_solid dissolve into A_bulk at rate constant k_1 in a solvent (this reaction can go in the two directions). A_solid dissolves into the solvent until A_bulk reaches the saturation A_sat. Moreover A_bulk reacts with a product B to give C at a rate constant k_2. This is the picture of the reaction:

Dissolution process
Dissolution process

This is the code I have written for the reaction:

library(deSolve)

# inputs 
T = 0  + 273.15 # K (Kelvin ) / tTemperature 
V = 50 # mL / Volume
A_solid = 125/V # mmol/mL = mol/L / initial concentration of product A_solid
B = 100/V # mol/L / initial concentration of product B

# parameters
R = 8.314 # J/(K*mol) / gas constant
expfact_sat = 2
E_a_sat = 10^3

params <- c(k1 = 0.1, # rate constant of dissolution
            k2 = 3*10^(-3), # rate constant of reaction
            A_sat = expfact_sat*exp(-E_a_sat/(R*T))) # saturation of the A_bulk into the solvent

# initial values
state <- c(A_solid = A_solid, A_bulk = 0, B = B, C = 0)

# system of differential equations
derivs <- function(t, y, parms) {
  with(as.list(c(y, parms)), {
    dA_solid = -k1*(A_sat - A_bulk)
    dA_bulk = -k2*A_bulk*B + k1*(A_sat - A_bulk)
    dB = -k2*A_bulk*B
    dC = k2*A_bulk*B
    return(list(c(dA_solid, dA_bulk, dB, dC)))
  })                                                        
}

times = seq(0, 500, by = 0.01)
init <- ode(y = state, func = derivs, time = times, parms = params)

l = dim(init)[1]-1
matplot(init[,1], init[,-1], type = "l", lty = 1:1, lwd = c(2),
        col = 1:l, xlab = "time [min]", ylab = "concn [mol/L]")
legend("topright", colnames(init)[-1], col = 1:l, lwd = c(2))

The problem here is that if you make the plot you will see that A_solid is going under 0, which means that the concentration of A_solid is negative, which is non-sense.

This is how the final plot should look like:

Dissolution
Dissolution

If you have any advice or solution how to handle this problem, this would be nice.

@BenBolker: The problem with the log transformation is, as you stated in your disadvantages, the concentration of A_solid goes in finite time to 0, and so I am not sure that we can still apply this technique.

P.S.: I am new to this forum and so I cannot display the images directly.

Lutz Lehmann
  • 25,219
  • 2
  • 22
  • 51
Anto
  • 1
  • 2
0

@Anto Add in an if statement to set it equal to zero

dA_solid = -k1*(A_sat - A_bulk)
if((A_solid+dA_solid)<0 {dA_solid = -1*(A_solid)}
dA_bulk = -k2*A_bulk*B + k1*(A_sat - A_bulk)