Use simultaneous ODE in STAN with a ridiculously simple example
Here is another example for simultaneous ODE is involved in the model. In this example, we assume the model with 1st order absorption. Carefully check the difference between this one and the previous example. You could get an overall view of what are necessary to use ODE in STAN.
1-compartment model for 1st order absorption
Consider the following 1-compartment model for 1st order absorption with log normal error. Let\(\hat{C}(t)\) be the concentration at time \(t\), \(\displaystyle k\left(:=\frac{CL}{V}\right)\) be the elimination constant, \(G(t)\) be the amount of drug in the absorption compartment, and \(k_a\) be the 1st order absorption constant, then we have the following simultaneous ODE: \begin{align} \left\{ \begin{array}{l} \displaystyle \frac{\partial G(t)}{\partial t}=-k_aG(t) \\ \displaystyle \frac{\partial \hat{C}(t)}{\partial t}=k_aG(t)-k\hat{C}(t) \end{array} \right. \label{eq:odeabs} \end{align}Data generation
Analytical solution for Eq.(\ref{eq:odeabs}) is: \begin{align} \hat{C}(t)=\frac{D}{V}\frac{k_a}{k_a-k} \left( \exp \left( -kt\right) - \exp \left( -k_at\right) \right), \label{eq:anasolabs} \end{align} where \(D\) is amount of dose (bioavailability is not considered here.) and \(V\) is volume of distribution. We can generate simulation dataset using Eq.(\ref{eq:anasolabs}) as follows: \begin{align} C(t)\sim \mbox{lognormal} \left( \log(\hat{C}(t)), \sigma \right), \label{eq:geneabs} \end{align} where \(\sigma\) is standard deviation of the measurement error. Suppose we measured from \(t=1.0\) to \(24.0\) by \(0.5\) hour and \(D=20\), \(V=30\), \(CL=6\), \(k_a=0.9\) and \(\sigma=0.05\).library(rstan) D = 20 theta=c(30,6,0.9) t = seq(1,24,0.5) sigma = 0.05 FOSD = function(theta,t){ V = theta[1] k = theta[2]/theta[1] ka = theta[3] res = D/V*ka/(ka-k)*(exp(-k*t)-exp(-ka*t)) return(res) } SCFOSD = function(theta,t,sigma){ yhat = FOSD(theta,t) res = yhat*exp(rnorm(length(yhat),0,sigma)) return(res) } res = c() for (i in 1:length(t)){ res = c(res,SCFOSD(theta,t[i],sigma)) } p = ggplot(data.frame(t, C=res)) p + geom_point(aes(x = t, y = C))The generated dataset is shown as below.
Implementation in STAN
The following STAN code is using the function "integrate_ode_rk45". The functions \(G(t)\) and \(\hat{C}(t)\) are assigned to \(y[1]\) and \(y[2]\) respectively. However, only \(y[2]\) is used in the model section. The parameter K in transformed parameters section is set in order to avoid flip-flop kinetics, i.e., \(k_a>k\).model_code_1_comp_abs=' functions { real[] absorp(real t, real[] y, real[] theta, real[] x_r, int[] x_i) { real dydt[2]; real k; real ka; k = theta[2]/theta[1]; ka = theta[3]; dydt[1] = -ka*y[1]; dydt[2] = ka*y[1]-k*y[2]; return dydt; } } data { intThen, we can do sampling by the following script. The model mathematically has 2 solutions (ill-identified condition) and "init_fun" is necessary to converged to correct solution and make it faster.T; real t0; real ts[T]; real y[T]; } transformed data { real x_r[0]; int x_i[0]; } parameters{ real sigma; real theta[3]; // V, CL, Ka } transformed parameters{ positive_ordered[2] K; real y0[2]; real y_hat[T, 2]; K[1]=theta[2]/theta[1]; K[2]=theta[3]; y0[1] = 20/theta[1]; y0[2] = 0; y_hat = integrate_ode_rk45(absorp, y0, t0, ts, theta, x_r, x_i); } model { for (i in 1:T) y[i] ~ lognormal(log(y_hat[i,2]),sigma); } ' model_1_comp_abs <- stan_model(model_code = model_code_1_comp_abs)
init_fun <- function(){list(theta=c(runif(1,10,50),runif(1,2,10),runif(1,0.5,2)))} fit <- sampling(model_1_comp_abs, data = list(T = length(t), ts = t, t0 = 0, y = res), chains = 1, iter = 1000, init=init_fun )We get the following estimates and all the parameters are almost accurately recovered.
> print(fit) Inference for Stan model: c9107cb9548c51f0c9f9926746ed55b0. 1 chains, each with iter=1000; warmup=500; thin=1; post-warmup draws per chain=500, total post-warmup draws=500. mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat sigma 0.05 0.00 0.01 0.04 0.05 0.05 0.06 0.06 219 1 theta[1] 30.64 0.03 0.45 29.77 30.32 30.64 30.94 31.46 225 1 theta[2] 6.09 0.00 0.06 5.97 6.04 6.09 6.13 6.23 250 1 theta[3] 0.88 0.00 0.04 0.80 0.85 0.88 0.91 0.98 302 1 K[1] 0.20 0.00 0.00 0.20 0.20 0.20 0.20 0.20 309 1 K[2] 0.88 0.00 0.04 0.80 0.85 0.88 0.91 0.98 302 1 y0[1] 0.65 0.00 0.01 0.64 0.65 0.65 0.66 0.67 227 1 y0[2] 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 500 NaN y_hat[1,1] 0.27 0.00 0.01 0.24 0.26 0.27 0.28 0.29 296 1 y_hat[1,2] 0.34 0.00 0.01 0.32 0.33 0.34 0.35 0.36 302 1 y_hat[2,1] 0.17 0.00 0.01 0.15 0.17 0.17 0.18 0.20 304 1 y_hat[2,2] 0.40 0.00 0.01 0.38 0.39 0.40 0.41 0.42 292 1 y_hat[3,1] 0.11 0.00 0.01 0.09 0.11 0.11 0.12 0.13 308 1 y_hat[3,2] 0.42 0.00 0.01 0.41 0.42 0.42 0.43 0.44 277 1 (snip) y_hat[45,1] 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 254 1 y_hat[45,2] 0.01 0.00 0.00 0.01 0.01 0.01 0.01 0.01 500 1 y_hat[46,1] 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 283 1 y_hat[46,2] 0.01 0.00 0.00 0.01 0.01 0.01 0.01 0.01 491 1 y_hat[47,1] 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 298 1 y_hat[47,2] 0.01 0.00 0.00 0.01 0.01 0.01 0.01 0.01 478 1 lp__ 118.65 0.13 1.48 115.07 117.85 118.99 119.76 120.51 135 1Not to mentioned, you can include individual variability with this ODE model with simple modifications.
Comments
Post a Comment