R- ode function (deSolve package): change the value of a parameter as a function of time

泪湿孤枕 提交于 2021-02-11 05:07:57

问题


I am trying to solve a first-order differential equation using the function ode from the deSolve package. The problem is as follows: a drug is administered by a constant infusion rate at some times (infusion times) and eliminated in a first-order rate. Thus, the process can be described by:

if(t %in% Infusion_times){Infusion <- Infusion_rate} else{Infusion <- 0}
    dC <- -Ke*C + Infusion

where t is the time, Infusion_times is a vector containing at what times the drug is administered, C is the quantity of the drug, Ke is its elimination constant and Infusion is a variable taking the value of the infusion rate when there is infusion, and the value 0 otherwise. So, let's say we want to administer 3 doses, starting at times 0, 24 and 40, with each infusion lasting for two hours, and let's say we want deSolve to compute the answer every 0.02 units of time. We want deSolve to solve the differential equation for the times between 0 and 48 with steps of 0.02 times unit, for instance. So our vector specifying the times to the ode function would be:

times <- seq(from = 0, to = 48, by = 0.02)

The infusion times are given by:

Infusion_times <- c(seq(from = 0, to = 2, by = 0.02), seq(from = 24, to = 26, by = 0.02), 
                    seq(from = 40, to = 42, by = 0.02))

At first, I was concerned that the problem could be in the comparison of floating-points. To prevent it, I rounded both vectors to two decimal places:

times <- round(times, 2)
Infusion_times <- round(times, 2)

So now, hopefully, all Infusion_times are included in the times vector:

(sum(Infusion_times %in% times)/length(Infusion_times))*100
[1] 100

As you can see, all the values in Infusion_times (100%) are contained in the vector times, and thus the variable Infusion should take the value Infusion_rate at the specified times. However, when we solve the equation, it doesn't work. Let's prove it, but first, let's specify the other values needed by the ode function:

parameters <- c(Ke = 0.5)
amounts <- c(C = 0) #Initial value for drug is 0
Inf_rate <- 5

And now, let's write a function stating the rate of changes, just as required:

OneComp <- function(t, amounts, parameters){
  with(as.list(c(amounts, parameters)),{
      if(t %in% Infusion_times){Infuse =Inf_rate} else{Infuse = 0}
      dC <- -Ke*C + Infuse
  list(c(dC))})
}

For those who are not familiar with the deSolve package, the argument t of the function will state the times for which the equation should be integrated, amounts will specify the initial value of C and parameters will give the value of the parameters (in this case, just Ke). And now, let's solve the equation:

out <- ode(func = OneComp, y = amounts, parms = parameters, times = times)

Let's plot the results:

library(ggplot2)

ggplot(as.data.frame(out)) + geom_line(aes(x = times, y = C))

This is exactly the same result we would get if Infusion was always equal to 0. However, note that if we were to mimic only a single dose, and we were to try a similar approach, it would work:

OneComp <- function(t, amounts, parameters){
      with(as.list(c(amounts, parameters)),{
          if(t < 2){Infuse =Inf_rate} else{Infuse = 0}
          dC <- -Ke*C + Infuse
      list(c(dC))})
    }
out <- ode(func = OneComp, y = amounts, parms = parameters, times = times)
ggplot(as.data.frame(out)) + geom_line(aes(x = times, y = C))

Here, we have made the variable Infuse take the value of the Inf_rate only when the time is less than 2 hours, and it works! Therefore, I am totally puzzled with these behaviour. It is not a problem of changing the value of a variable, it is not a problem of a comparison between floating-point numbers... Any idea of what could these be? Thanks


回答1:


I have been struggling with the same problem for some time. I was trying to replicate IV infusion followed by PO dosing using the deSolve package rather than the RxODE package used in the original model. My solution was to make a list of infusion times and later extract a maximal value:

tmp <- c()
for (i in seq(from = 1, to = Num.Doses, by = 1)) {
tmp[i] <- (i * Tau) - Tau
tmp2 <- seq(from = 0, to = 2, by = 0.1)
Inf.Times <- round(unlist(lapply(tmp, function(x, Add) x + tmp2)), 3)}

Here, Num.Doses is set for 5 IV infusions. The Tau (dosing interval) is 24 hours, 0 is infusion start time, and 2 is infusion end time in hours.

Next I constructed a model, the full version of which is a multicompartment PKPD model from RxODE (https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4728294/):

Model <- function(time, yini, parameters) {
  with(as.list(c(yini, parameters)), {
    Infusion <- ifelse(time %% Tau < Inf.Time & time <= max(Inf.Times), Dose / Inf.Time, 0)
    C1 <- Central / V1
    dDepot <- - (Ka * Depot)
    dCentral <- (Ka * Depot) - (CL * C1) + Infusion
    list(c(dDepot, dCentral))})}

I got the idea for the Infusion <- line from Andrew Booker as shown at https://github.com/andrewhooker/PopED/issues/11. I modified his suggestion from if else to ifelse and incorporated a hard stop after the end of the 5th infusion with the time <= max(Inf.Times) otherwise the model would continually re-infuse. That also allowed me to implement additional non-IV dosing using an events table when running the model using deSolve:

Dose.Events <- data.frame(var = "Depot", time = c(120, 144, 168, 192, 216), value = Dose2, method = "add")
Times <- seq(from = 0, to = Tot.Hours, by = 1)
out <- ode(y = Ini.Con, times = Times, func = Model2, parms = Parms, events = list(data = Dose.Events))

When run using the full model, the output is nearly the same as using the original code in RxODE, which is more straightforward and "clean". The differences, as judged by AUC, were minimal with sig figs the same out to 6 digits. I am able to replicate the IV infusions (first set of 5 peaks) and also recapitulate the PO dosing (second set of 5 peaks).




回答2:


Most solvers of deSolve use an automatic internal time step, that adjusts itself, depending on the roughness or smoothness of the system. The use of if statements or if()functions in the model function is not a good idea for two reasons: (i) the time steps may not be hit exactly and (2) the model function (i.e. the derivative) should avoid step-wise behavior, even if the solvers are quite robust in such cases.

The deSolve package provides are two approaches for your problem: "forcing functions" and "events". Both have their pros and cons, but "events" are especially useful if the time for the "event" (e.g. an injections) is very short, compared to the integration time step.

More about this can be found in the deSolve help pages ?forcings and ?events, in deSolve: Forcing functions and Events from the useR!2017 conference, and in the slides from userR!2014.

Please check if the following works for you:

library("deSolve")

OneComp <- function(t, y, parms){
  with(as.list(c(y, parms)),{
    dC <- -Ke * C
    list(c(dC))
  })
}

eventfunc <- function(t, y, parms) {
  with(as.list(c(y, parms)),{
    C + Inf_rate
  })
}

parms <- c(Ke = 0.5, Inf_rate = 5)

y0 <- c(C = 0)            # Initial value for drug is 0

Infusion_times <- c(seq(from =  0, to =  2, by = 0.02), 
                    seq(from = 24, to = 26, by = 0.02), 
                    seq(from = 40, to = 42, by = 0.02))

# time step can be made bigger to speedup simulation
times <- round(seq(from = 0, to = 48, by = .1) , 2)

# check that all events are in 'times', but no duplicates
# this check is also done by the solver and may print a warning
# to ensure that the user is very careful with this
unique_times <- cleanEventTimes(times, Infusion_times)
times        <- sort(c(unique_times, Infusion_times))

out <- ode(func = OneComp, y = y0, parms = parms, times = times, 
           events = list(func = eventfunc, time = Infusion_times))

plot(out)
rug(Infusion_times)

The two cleanEventTimes lines are one possible approach for ensuring that all event times are hit by the simulation. It is normally done automatically by the solver and may issue a warning to remind the user to be very careful with this.

I used "base" plot and rug to indicate injection times.

I wonder somewhat about the terms Infusion_times and Inf_rate. In an event-based approach, an "amount" is added to the state variable C at discrete time points, while a "rate" would indicate continuous addition within a time interval. This is often called a forcing function.

A forcing function would be even simpler and is numerically better.



来源:https://stackoverflow.com/questions/61484483/r-ode-function-desolve-package-change-the-value-of-a-parameter-as-a-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!