--- title: "One-compartment PK model" author: "Nan-Hung Hsieh" date: "`r Sys.Date()`" bibliography: references.bib output: rmarkdown::html_vignette: toc: true toc_depth: 3 vignette: > %\VignetteIndexEntry{One-compartment PK model} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} library(pksensi) #mcsim_install(version = "6.1.0") knitr::opts_chunk$set( eval = F, dev = "png", dpi = 200, fig.align = "center", fig.width = 7, out.width = "90%", fig.height = 4, comment = "#>" ) ``` In this example, we use a generic, one-compartment PK model from **httk** package [@JSSv079i04] to demonstrate how **pksensi** can be applied to pharmacokinetic studies. ## Equations The differential equations for the one-compartment PK model can be written as: $$\frac{dA_{gutlumen}}{dt} = -k_{gutabs} \cdot A_{gutlumen} + g(t)$$ $$\frac{dC_{rest}}{dt} = \frac{k_{gutabs}}{V_{dist}}-k_{elim} \cdot C_{rest}$$ where $A_{gutlumen}$ is the state variable that describes the quantity of compound in the gut lumen (mg) and $A_{rest}$ is the quantity of compound in rest of body and blood (mg). The parameter $k_{gutabs}$ is the absorption rate constant that describes the chemical absorption from the gut lumen into gut tissue through first-order processes (/h) and $k_{elim}$ is the elimination rate constant (/h), which is equal to the total clearance divided by the volume of distribution. The time-dependent function $g(t)$ is used to describe the oral dosing schedule. The concentration of the chemical in the rest of body and blood ($C_{rest}$, mg/L) can be calculated as $$ C_{rest} = A_{rest} / V_{dist} \cdot BW$$ where $V_{dist}$ is the volume of distribution (L/kg BW) and $BW$ is the body weight (kg). The $C_{rest}$ can also be seen as the chemical concentration in plasma that can be further used to compare with observed results in a pharmacokinetic experiment. The bioavailability is assumed to be 100% in this model. ## Model implementations with **deSolve** solving engine To start, we implemented the one-compartment PK model in R. The **pksensi** allows users select the preferred method to solve the PK model, either with the **deSolve** [@JSSv033i09] package or with **GNU MCSim** [@JSSv002i09] through the compile function. This section will show how to conduct global SA with pure R and GNU MCSim model code. ### Pure R model code The one-compartment PK model can describe the quantity of compound in the gut lumen (`Agutlument`) and the rest of body (`Acompartmant`). The `Ametabolized` is the quantity of compound transform and metabolize through hepatic clearance. The model mainly includes two state variables that are the quantity of compound in the gut lumen (`Agutlument`) and the rest of body (`Acompartmant`). The `Ametabolized` is the quantity of compound transform and metabolize through hepatic clearance. ```{r} pbtk1cpt <- function(t, state, parameters) { with(as.list(c(state, parameters)), { dAgutlument = - kgutabs * Agutlument dAcompartment = kgutabs * Agutlument - ke * Acompartment dAmetabolized = ke * Acompartment Ccompartment = Acompartment / vdist * BW; list(c(dAgutlument, dAcompartment, dAmetabolized), "Ccompartment" = Ccompartment) }) } ``` The parameter values and initial states need to be assigned to specific values before simulation. Here, we use the corresponding parameter value of acetaminophen in this example. These model parameters are derived from the *in-vivo* or *in-vitro* experiment results. The parameter value can be generated from `parameterize_1comp` function in **httk** package as: ```{r, warning=F} library(httk) pars1comp <- (parameterize_1comp(chem.name = "acetaminophen")) unlist(pars1comp) ``` ```{r} parms <- c(vdist = pars1comp$Vdist, ke = pars1comp$kelim, kgutabs = pars1comp$kgutabs, BW = pars1comp$BW) initState <- c(Agutlument = 10, Acompartment = 0, Ametabolized = 0) parms ``` The given value of `vdist`, `ke`, and `kgutabs` in **httk** are 1.1 (L/kg BW), 0.23 (/h), and 2.18 (/h), respectively. The body weight is assumed to be 70 (kg). Here shows the given parameter value (`parms`) and initial state condition (`initState`) that need to specify in model solving. Both `parms` and `initState` are "numeric variables" that contain the value of parameter and initial state condition. Using the `ode()` function in **deSolve** package, we can visualize the PK profile according to the given parameter baseline and the time points (`t`). ```{r} library(deSolve) t <- seq(from = 0.01, to = 24.01, by = 1) y <- ode(y = initState, times = t, func = pbtk1cpt, parms = parms) ``` ```{r, fig.height=6, fig.width=5} plot(y) ``` Setting up the parameter distributions. The distribution of parameter is taken to be uniform with bounds corresponding to 50% and 200% of the nominal value. ```{r} params <- c("vdist", "ke", "kgutabs", "BW") q <- c("qunif", "qunif", "qunif", "qnorm") q.arg <- list(list(min = pars1comp$Vdist / 2, max = pars1comp$Vdist * 2), list(min = pars1comp$kelim / 2, max = pars1comp$kelim * 2), list(min = pars1comp$kgutabs / 2, max = pars1comp$kgutabs * 2), list(mean = pars1comp$BW, sd = 5)) q.arg ``` The parameter ranges are assumed to be 0.55 and 2.2 L/kg BW for `vdist`. The `ke` are ranged from 0.12 to 0.47 /h, corresponding to half-times of 1.5 and 5.8 hr. The `ka` are ranged 1.09 to 4.36 /h. The `BW` is assumed to a normal distribution with mean = 70 kg and sd = 5 kg. Here, we set a sample size of 200 with 10 replications. Through `rfast99()` function, a S3 object with class `rfast99` will be created. The `set.seed()` can use to reproduce the same parameter matrix in the random sampling. The sample size determines the robustness of the result of SA. Higher number of sample size lead to narrower confidence intervals for sensitivity measurements across different replications. However, it will take a longer time in computation. ```{r} set.seed(1234) x <- rfast99(params, n = 200, q = q, q.arg = q.arg, replicate = 10) ``` The generated parameters are stored as a 3-D array under the named `a`, with the dimension of sample size, the number of replications, and the number of parameters, respectively. ```{r} dim(x$a) ``` The sample number is 200, with 4 model parameters, which generates 800 model evaluations. The replication is set to 10. Therefore, the total of 8,000 parameter sequence will be used to compute the corresponding outputs. Figure \ref{fig:rep-sampl} plotted the sampling process for each parameter from the first 3 replications. The search curves show the different intensity of sampling patterns in each segment. ```{r, fig.height=5} par(mfrow=c(4,4),mar=c(0.8,0.8,0.8,0),oma=c(4,4,2,1), pch =".") for (j in c("vdist", "ke", "kgutabs", "BW")) { if ( j == "BW") { plot(x$a[,1,j], ylab = "BW") } else plot(x$a[,1,j], xaxt="n", ylab = "") for (i in 2:3) { if ( j == "BW") { plot(x$a[,i,j], ylab = "", yaxt="n") } else plot(x$a[,i,j], xaxt="n", yaxt="n", ylab = "") } hist <- hist(x$a[,,j], plot=FALSE, breaks=seq(from=min(x$a[,,j]), to=max(x$a[,,j]), length.out=20)) barplot(hist$density, axes=FALSE, space=0, horiz = T, main = j) } mtext("Model evaluation", SOUTH<-1, line=2, outer=TRUE) ``` Because the PK model is being used to describe a continuous process for the chemical concentration over time, the sensitivity measurements, therefore, have the time-dependent relationships for each model parameter. Here we use the defined output time points (`t`) to examine the change of the parameter sensitivity over time. To solve the model through **deSolve**, we need to provide the details of the argument, which include time (`t`), initial conditions of state variable (`initState`), output variables (`outnames`), and name of the model function (`func`). To create the time-dependent sensitivity measurement, we set the time duration from 0.01 to 24.01 hours with the time segment of 1 hour as the above definition in `ode()` function in this example. The initial time point should avoid 0 to prevent computational error in misconduct. The `outnames` is based on the arguments from the `ode()` function in **deSolve** package. ```{r} outputs <- c("Ccompartment", "Ametabolized") out <- solve_fun(x, time = t, func = pbtk1cpt, initState = initState, outnames = outputs) ``` The output result `out` is an S3 object of `rfast99()` as well, which can link with `print()`, `plot()`, and `check()` method to examine the sensitivity measurements. The `print()` function gives the sensitivity and convergence indices for main, interaction, and total order at each time point. In addition to print out the result of SA, the more efficient way to distinguish the influence of model parameter is to visualize these indices. ```{r} plot(out) ``` The SI has computed range from 0 (no impact) to 1 (high impact) and represents the contribution percentage of output variance under the given parameter distributions. The solid line represents the total (black) and first (red) order SI with 95% confidence interval (polygon). The dashed line is the cut-off with the default value of 0.05. Here, we can see that `vdist` and `ke` dominate the plasma concentration before and after 5-hr post chemical intake, respectively. The parameter `kgutabs` only plays a crucial role to determine the plasma concentration in the first hour. However, the current result only based on the distribution of model parameters for APAP. Given different input conditions (e.g., range of parameter uncertainty, chemical-dependent parameter value) the result can of course change (result not shown). The default output in the plotting is setting at the first variable. To exam the time-dependent SI of other variables, such as `Ametabolized` in this case, we need to assign the variable name `vars = "Ametabolized"` in `plot()` function. ```{r} plot(out, vars = "Ametabolized") ``` The amount of metabolized is also determined by parameter `ke`. Same as `Ccompartment`, the `kgutabs` contribute about 30 - 40% variation of model output in the first hour. The `BW` is the least important parameter in the current analysis, and therefore, can be fixed in the model fitting to data and additional applications. In addition to using the time-SI profile to investigate the parameter impact on model output, we can directly examine the relationship between parameters and model output graphically. ```{r, fig.height=5} par(mfcol=c(4,4),mar=c(0.8,0.8,0,0),oma=c(4,4,2,1), pch = ".") plot(x$a[,1,"vdist"], out$y[,1,"0.01",1], xaxt="n", main = "\nvdist") plot(x$a[,1,"vdist"], out$y[,1,"2.01",1], xaxt="n") plot(x$a[,1,"vdist"], out$y[,1,"6.01",1], xaxt="n") plot(x$a[,1,"vdist"], out$y[,1,"24.01",1]) for (j in c("ke", "kgutabs", "BW")){ for (k in c("0.01", "2.01", "6.01", "24.01")){ if (k == "0.01") { plot(x$a[,1,j], out$y[,1,k,1], yaxt = "n", xaxt="n", main = paste0("\n", j)) } else if (k == "24.01") { plot(x$a[,1,j], out$y[,1,k,1], yaxt = "n") } else plot(x$a[,1,j], out$y[,1,k,1], xaxt = "n", yaxt = "n") } } mtext("Parameter", SOUTH<-1, line=2, outer=TRUE) mtext("Ccompartment", WEST<-2, line=2, outer=TRUE) ``` Here shows the relationship between the concentration of the rest of the body (`Ccompartment`) and the model parameters at times 0.01, 2.01, 6.01, and 24.01 hr (top to bottom), respectively. We can find that `kgutabs` and `vdist` have higher correlation with `Ccompartment` in the beginning (t = 0.01 h) of post-intake duration compared with other parameters, suggesting that the parameters have high impact on the modeling result. The `ke` shows a high correlation at the later time period (t = 24.01 h). The parameter `BW` does not show any obvious relationship with `Ccompartment`. The output variable `out` containing all the input arguments detailed above and the calculated SI of first order (`mSI`), interaction (`iSI`), and total order (`tSI`). Convergence indices are also stored in the list named `mCI`, `iCI`, and `tCI`. The outputs are formatted as 4-D array in `y` with the dimension name of model evaluation, number of replications, number of time points, and number of output variables, respectively. ```{r} dim(out$y) ``` Some functions in **pksensi** provide efficient ways to check the result from global SA. The `check()` can determine which parameters have relatively lowered sensitivity measurement across the given time points and model outputs, and therefore can be applied parameter fixing in model calibration. The `check()` also provides an argument to specify the target output or change the cut-off value. The argument of `cutoff` set for example at 0.05, is used to detect the relative non-influential parameters as default, in this case representing a 5% change of the output is contributed from the specific parameter variation. ```{r} check(out, cutoff = 0.05) ``` Based on the sensitivity measurement of the total order, the result shows that `BW` has a relative lower measurement of SI. However, all parameters do not converge to the setting cut-off, which means the larger sample size is required in further sensitivity testing. Similar to the `plot()` function that can assign specific output variable in the examination, the `check()` function can also use the assignment (`vars`) to examine a given output. ### GNU MCSim model code Running model under **GNU MCSim** native code can have a faster speed to obtain the model outputs. This subsection will show how to conduct global SA with **deSolve** package with **GNU MCSim** model code. ```{r} pbtk1cpt_model() cat(readLines("pbtk1cpt.model"), sep = "\n") ``` The code must first be compiled to run the model. After create the model file, we can use `compile_model()` function to generate the file that has dynamic-link library (DLL) or share object (SO) extension and can be linked dynamically into an R session (`pbtk1cpt.dll` on Windows or `pbtk1cpt.so` on other systems) and R file (`pbtk1cpt_inits.R`) with default input parameters and initial state settings with the definition of `application = "R"`. ```{r, message=FALSE, warning=FALSE} mName <- "pbtk1cpt" compile_model(mName, application = "R") ``` The `pbtk1cpt_inits.R` file includes `initParms` and `initStates` functions and `Outputs` variable. The created function have default value of model parameters and initial conditions that can further use to customize in the simulation. ```{r} source(paste0(mName, "_inits.R")) ``` The parameter values and initial states can be customized to the specific condition. It can also schedule for the given dosing scenario. In the current setting, we assumed the initial condition of the intake dose to be 1000 mg. We can use `initParms` and `initStates` functions to customize the parameter values and the initial state that will be used in the following modeling and SA. These additional functions are generated when compiling the model file. We used the the same parameter values in this section. ```{r} parms <- initParms() parms["vdist"] <- pars1comp$Vdist parms["ke"] <- pars1comp$kelim parms["kgutabs"] <- pars1comp$kgutabs parms["BW"] <- pars1comp$BW initState <- initStates(parms=parms) initState["Agutlument"] <- 10 ``` Here shows the given parameter value (`parms`), initial state condition (`initStat`), and the output variable (`Outputs`) that need to specify in model solving. ```{r} parms ``` ```{r} initState ``` ```{r} Outputs ``` Using the `ode()` function in **deSolve** package. Unlike above example, we have to assign additional arguments, such as `dllname`, `initfunc`, `nout`, and `outnames`. ```{r} t <- seq(from = 0.01, to = 24.01, by = 1) y <- ode(y = initState, times = t, func = "derivs", parms = parms, dllname = mName, initfunc = "initmod", nout = 1, outnames = Outputs) ``` ```{r} head(y) ``` Same as above, use the following code to define the parameter distributions and generate parameter matrix. ```{r} # Define parameter distribution q <- c("qunif", "qunif", "qunif", "qnorm") q.arg <- list(list(min = parms["vdist"] / 2, max = parms["vdist"] * 2), list(min = parms["ke"] / 2, max = parms["ke"] * 2), list(min = parms["kgutabs"] / 2, max = parms["kgutabs"] * 2), list(mean = parms["BW"], sd = 5)) params <- c("vdist", "ke", "kgutabs", "BW") # Generate parameter matrix set.seed(1234) x <- rfast99(params, n = 200, q = q, q.arg = q.arg, replicate = 10) ``` Run simulation and check result. ```{r} outputs <- c("Ccompartment", "Ametabolized") out <- solve_fun(x, time = t, initState = initState, outnames = outputs, dllname = mName) check(out) ``` The simulation time had huge improvement when using **GNU MCSim** model code. ## Model Implementation with **GNU MCSim** solving engine In addition to use **deSolve** to solve differential equations in PK model, the **GNU MCSim** can provide better computational efficiency. To solve ODE through **GNU MCSim**, we need to change the argument to `application = mcsim` in `compile_model()` function. The computing time of using `solve_fun()` function in SA is estimated as, ```{r} system.time(out<-solve_fun(x, t, initState = initState, outnames = Outputs, dllname = mName)) ``` Then, before we conduct the SA through **GNU MCSim**, The following code is used to compile the **GNU MCSim** model code to the executable program. ```{r, message=F, warning=F} compile_model(mName, application = "mcsim") ``` Similar to `solve_fun()` function that can define the initial value of parameter and state variable through generated functions, the `solve_mcsim()` also has a `condition` argument that can be used to give the specific input value such as exposure dose, fixed parameter value or initial condition of state variable. ```{r} conditions <- c("Agutlument = 10") system.time(out <- solve_mcsim(x, mName = mName, params = params, vars = Outputs, time = t, condition = conditions)) ``` After solving the equations under the same given condition, we can find that **GNU MCSim** provide faster speed in computing performance than using **deSolve**. In this case, we only focus on performing the global SA alone for generic PK model without additional comparison with experiment data. The PBPK example will display and reproduce our previous published result [@fphar201800588] with full global SA workflow. ## Session Info ```{r} sessionInfo() ``` ## References