问题
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