Updated The problem is solved with the answer from @tpetzoldt, the original code has been modified to run the fit successfully.
Hi everybody, I'm trying to fit the experimental data on a set of 3 PDEs to find 4 coefficients including mumax, Ks, Y_(x/s), and Y_(p/s). The code I used worked with the set of 2 PDEs but is not working with this set of 3. The following is the code:
The set of PDEs needs to be fitted:
dX/dt = mumax . S . X / (Ks + S)
dS/dt = -1/Y_(x/s) . mumax . S . X / (Ks + S)
dP/dt = alpha . dX/dt + beta . X
library(deSolve)
library(ggplot2)
library(minpack.lm)
library(reshape2)
time <- c(0, 3, 5, 8, 9.5, 11.5, 14, 16, 18, 20, 25, 27)
X <- c(0.0904, 0.1503, 0.2407, 0.3864, 0.5201, 0.6667, 0.8159, 0.9979, 1.0673, 1.1224, 1.1512, 1.2093)
S <- c(9.0115, 8.8088, 7.9229, 7.2668, 5.3347, 4.911, 3.5354, 1.4041, 0, 0, 0, 0)
P <- c(0.0151, 0.0328, 0.0621, 0.1259, 0.2949, 0.3715, 0.4199, 0.522, 0.5345, 0.6081, 0.07662, 0.7869)
data <- data.frame(time, X, S, P)
Monod <- function(t,c,parms) {
k1 <- parms$k1 # mumax
k2 <- parms$k2 # Ks
k3 <- parms$k3 # Y_(X/S)
k4 <- parms$k4 # alpha
k5 <- parms$k5 # beta
r <- numeric(length(c))
r[1] <- k1 * c["S"] * c["X"] / ( k2 + c["S"] ) # r[1] = dCx/dt = k1.Cs.Cx/(k2+Cs)
r[2] <- -k1 * c["S"] * c["X"] / ( ( k2 + c["S"] ) * k3 ) # r[2] = dCs/dt = -1/k3 * dCx/dt
r[3] <- k4 * r[1] + k5 * c["X"] # r[3] = dCp/dt = alpha * dX/dt + beta * X
return(list(r))
}
residuals <- function(parms){
cinit <- c( X=data[1,2], S=data[1,3], P=data[1,4] )
t <- c(seq(0, 27, 1), data$time)
t <- sort(unique(t))
k1 <- parms[1]
k2 <- parms[2]
k3 <- parms[3]
k4 <- parms[4]
k5 <- parms[5]
out <- ode( y = cinit,
times = t,
func = Monod,
parms = list( k1 = k1, k2 = k2, k3 = k3, k4 = k4, k5 = k5) )
out_Monod <- data.frame(out)
out_Monod <- out_Monod[out_Monod$t %in% data$time,]
pred_Monod <- melt(out_Monod,id.var="time",variable.name="Substance",value.name="Conc")
exp_Monod <- melt(data,id.var="time",variable.name="Substance",value.name="Conc")
residuals <- pred_Monod$Conc-exp_Monod$Conc
return(residuals)
}
parms <- c(k1=0.5, k2=6.5, k3=0.2, k4=1.2, k5 = 0.1)
fitval <- nls.lm(par=parms,fn=residuals)
cinit <- c(X=data[1,2], S=data[1,3], P=data[1,4])
out <- ode(y=cinit, times=seq(min(data$time), max(data$time)), func = Monod, parms=as.list(coef(fitval)))
plot(out, obs=data, mfrow=c(1, 3))
The original code had several issues:
t
in the data and time
in the ode outputConcentration
in one place and conc
in anotherssq
and ssqres
were misleading as the function calculates the residuals and not the square and not a sum. The sum of squares is calculated in the background (see ?nls.lm
)<-
and =
were inconsistently used. This is not an error, but not really good style.k1, K2, ...
one can directly use original names like mumax
or alpha
.data2
or Monod2
were used as there was no data1
.Fixing all this, we come to the solution below. It runs through and fits somehow, but it can still be improved. The warnings may be ignored for now. The reason is, that parameters exceed the allowed range. This can of course be fixed but would be another question.
library(deSolve)
library(ggplot2)
library(minpack.lm)
library(reshape2)
data <- data.frame(
time = c(0, 3, 5, 8, 9.5, 11.5, 14, 16, 18, 20, 25, 27),
X = c(0.0904, 0.1503, 0.2407, 0.3864, 0.5201, 0.6667, 0.8159, 0.9979, 1.0673, 1.1224, 1.1512, 1.2093),
S = c(9.0115, 8.8088, 7.9229, 7.2668, 5.3347, 4.911, 3.5354, 1.4041, 0, 0, 0, 0),
P = c(0.0151, 0.0328, 0.0621, 0.1259, 0.2949, 0.3715, 0.4199, 0.522, 0.5345, 0.6081, 0.07662, 0.7869)
)
Monod <- function(t, c, parms) {
k1 <- parms$k1 # mumax
k2 <- parms$k2 # Ks
k3 <- parms$k3 # Y_(X/S)
k4 <- parms$k4 # alpha
k5 <- parms$k4 # beta
r <- numeric(length(c))
r[1] <- k1 * c["S"] * c["X"] / (k2 + c["S"])
r[2] <- -k1 * c["S"] * c["X"] / ((k2 + c["S"]) * k3)
r[3] <- k4 * r[1] + k5 * c["X"]
return(list(r))
}
cinit <- c(X = data[1, 2], S = data[1, 3], P = data[1, 4])
t <- seq(0, 27, 1)
parms <- list(k1=0.1, k2=5, k3=1, k4=0.08, k5 = 0.005)
out <- ode(y=cinit, times=t, func = Monod, parms=parms)
plot(out)
residuals <- function(parms){
cinit <- c(X = data[1, 2], S = data[1, 3], P = data[1, 4] )
# time points for which conc is reported including the points where data is available
t <- c(seq(0, 27, 1), data$t)
t <- sort(unique(t))
# parameters from the parameter estimation routine:
k1 <- parms[1]
k2 <- parms[2]
k3 <- parms[3]
k4 <- parms[4]
# solve ODE for a given set of parameters
out <- ode(y = cinit, times = t, func = Monod, parms = list( k1 = k1, k2 = k2, k3 = k3, k4 = k4) )
# Filter data that contains time points where data is available:
out_Monod <- data.frame(out)
out_Monod <- out_Monod[out_Monod$t %in% data$t,]
# Evaluate predicted vs experimental residual
pred_Monod <- melt(out_Monod, id.var="time", variable.name="Substance", value.name="conc")
exp_Monod <- melt(data, id.var="time", variable.name="Substance", value.name="conc")
residuals <- pred_Monod$conc - exp_Monod$conc
# return predicted vs experimental residual
return(residuals)
}
# initial guess for parameters
parms <- c(k1=0.5, k2=6.5, k3=0.2, k4=1.2)
fitval <- nls.lm(par=parms, fn=residuals)
summary(fitval)
out <- ode(y=cinit, times=seq(min(data$time), max(data$time)), func = Monod, parms=as.list(coef(fitval)))
plot(out, obs=data, mfrow=c(1, 3))