|
| 1 | +## Load Libraries ----- |
| 2 | +rm(list=ls()) |
| 3 | +library(dMod) |
| 4 | +library(dplyr) |
| 5 | +library(ggplot2) |
| 6 | +theme_set(theme_dMod()) |
| 7 | +setwd(tempdir()) # change if you want to keep the files that dMod creates internally |
| 8 | +set.seed(5555) |
| 9 | + |
| 10 | +## Set up the ODE model ----------------------------------------------------------------------- |
| 11 | + |
| 12 | +# Define via reactions |
| 13 | + |
| 14 | +reactions <- eqnlist() %>% |
| 15 | + addReaction("TCA_buffer", "TCA_cell", rate = "import*TCA_buffer", description = "Uptake") %>% |
| 16 | + addReaction("TCA_cell", "TCA_buffer", rate = "export_sinus*TCA_cell", description = "Sinusoidal export") %>% |
| 17 | + addReaction("TCA_cell", "TCA_cana", rate = "export_cana*TCA_cell", description = "Canalicular export") %>% |
| 18 | + addReaction("TCA_cana", "TCA_buffer", rate = "reflux*TCA_cana", description = "Reflux into the buffer") |
| 19 | + |
| 20 | +# Define via ODE system (equivalent) |
| 21 | + |
| 22 | +# f <- eqnvec(TCA_buffer = "-import * TCA_buffer + export_sinus * TCA_cell + reflux * TCA_cana", |
| 23 | +# TCA_cana = "export_cana * TCA_cell - reflux * TCA_cana", |
| 24 | +# TCA_cell = "import * TCA_buffer - export_sinus * TCA_cell - export_cana * TCA_cell") |
| 25 | + |
| 26 | +# Translate reactions into ODE model object |
| 27 | +mymodel <- odemodel(reactions, modelname = "bamodel", compile = T) |
| 28 | + |
| 29 | + |
| 30 | +# Generate trajectories for the default condition |
| 31 | +x <- Xs(mymodel, condition = NULL) |
| 32 | + |
| 33 | + |
| 34 | +# For demonstration define parameters (initials and dynamic parameters) |
| 35 | +pars <- c(reflux = 0.1, TCA_buffer = 1, TCA_cell = 0, TCA_cana = 0, import = 0.2, export_sinus = 0.2, export_cana = 0.04) |
| 36 | + |
| 37 | + |
| 38 | +# Plot trjectories |
| 39 | +times <- seq(0, 50, 0.01) |
| 40 | +out <- x(times, pars) |
| 41 | +plot(out) # Note this is a ggplot object |
| 42 | + |
| 43 | +# Define observables buffer and cellular |
| 44 | +observables <- eqnvec(buffer = "s*TCA_buffer", cellular = "s*(TCA_cana + TCA_cell)") |
| 45 | +g <- Y(observables, f = x, condition = NULL, compile = TRUE, modelname = "obsfn") |
| 46 | + |
| 47 | + |
| 48 | + |
| 49 | +## Simulate data ------------------------------------------------------------------------------------------------------------- |
| 50 | + |
| 51 | +# Fix parameters to steady state values (calculated on paper / by hand), replace buffer -> TCA_buffer = 0 |
| 52 | +pars["TCA_cell"] <- 0.3846154 |
| 53 | +pars["TCA_cana"] <- 0.1538462 |
| 54 | +pars["TCA_buffer"] <- 0 |
| 55 | +pars["s"] <- 1e3 |
| 56 | + |
| 57 | +# Simulate trajectories |
| 58 | +# Evaluate the expression separately |
| 59 | +out <- (g * x)(times, pars, conditions = "closed") |
| 60 | +plot(out) |
| 61 | + |
| 62 | + |
| 63 | + |
| 64 | + |
| 65 | + |
| 66 | +# simulate data for time points |
| 67 | +timesD <- c(0.1, 1, 3, 7, 11, 15, 20, 41) |
| 68 | +datasheet <- subset(as.data.frame(out), time %in% timesD & name %in% names(observables)) |
| 69 | +datasheet <- within(datasheet, { |
| 70 | + sigma <- 0.1*value |
| 71 | + value <- rnorm(length(value), value, sigma) |
| 72 | +}) |
| 73 | +data <- as.datalist(datasheet) |
| 74 | +plot(out, data) |
| 75 | + |
| 76 | + |
| 77 | +# Define parameter transformations using define(), insert() and branch(). Old function repar also avaiable! |
| 78 | +innerpars <- getParameters(x,g) |
| 79 | +trafo <- NULL %>% |
| 80 | + define("x~x", x = innerpars) %>% # identity |
| 81 | + define("TCA_buffer~0") %>% |
| 82 | + insert("x~exp(log(10)*x)", x = .currentSymbols) |
| 83 | + |
| 84 | + |
| 85 | +# # Explicit trafo |
| 86 | +# trafo <- eqnvec(TCA_buffer = "0", |
| 87 | +# TCA_cell = "exp(log(10)*TCA_cell)", |
| 88 | +# TCA_cana = "exp(log(10)*TCA_cana)", |
| 89 | +# import = "exp(log(10)*import)", |
| 90 | +# export_sinus = "exp(log(10)*export_sinus)", |
| 91 | +# export_cana = "exp(log(10)*export_cana)", |
| 92 | +# reflux = "exp(log(10)*reflux)", |
| 93 | +# s = "exp(log(10)*s)") |
| 94 | + |
| 95 | + |
| 96 | +p <- P(trafo, condition = "closed") |
| 97 | +# Set every parameter to -1 in the log-space |
| 98 | +outerpars <- getParameters(p) |
| 99 | +pouter <- structure(rep(-1, length(outerpars)), names = outerpars) |
| 100 | +plot((g*x*p)(times, pouter),data) |
| 101 | + |
| 102 | + |
| 103 | +## Use simulate data to calibrate outer model parameters ------------------------------------------------------------- |
| 104 | +# # One Fit |
| 105 | +obj <- normL2(data, g * x * p) + constraintL2(pouter, sigma = 20) |
| 106 | + |
| 107 | +# Fit on time (starting from pouter) |
| 108 | +myfit_1 <- trust(obj, pouter, rinit = 0.1, rmax = 5) |
| 109 | +mypred_1 <- (g * x * p)(times, myfit_1$argument) |
| 110 | +plot(mypred_1, data) |
| 111 | + |
| 112 | +## Handling different experimental conditions |
| 113 | + |
| 114 | +# Define reflux and open condition according to "Dynamic Modelling in R p. 19-20" |
| 115 | +pars["reflux"] <- 1e3 |
| 116 | +out <- (g * x)(times, pars, conditions = "open") |
| 117 | +datasheet <- subset(as.data.frame(out), time %in% timesD & name %in% names(observables)) |
| 118 | +datasheet <- within(datasheet, { |
| 119 | + sigma <- 0.1*value |
| 120 | + value <- rnorm(length(value), value, sigma) |
| 121 | +}) |
| 122 | +data <- data + as.datalist(datasheet) |
| 123 | +plotData(data) |
| 124 | + |
| 125 | +# Parameter Trafo, usage of "+" operator for trafo functions (output of P()) |
| 126 | +trafo <- getEquations(p, conditions = "closed") |
| 127 | +trafo["reflux"] <- "exp(log(10)*reflux_open)" |
| 128 | +p <- p + P(trafo, condition = "open") |
| 129 | + |
| 130 | +outerpars <- getParameters(p) |
| 131 | +pouter <- structure(rep(-1, length(outerpars)), names = outerpars) |
| 132 | +plot((g*x*p)(times, pouter),data) |
| 133 | + |
| 134 | + |
| 135 | +# Objective function |
| 136 | +obj <- normL2(data, g * x * p) + constraintL2(pouter, sigma = 20) |
| 137 | + |
| 138 | +# Evaluation of obj at pouter |
| 139 | +obj(pouter) |
| 140 | + |
| 141 | + |
| 142 | +# Fit 50 times, sample with sd=4 around pouter |
| 143 | +out_frame <- mstrust(obj, pouter, sd = 4, studyname = "bamodel", cores=8, fits=50, iterlim = 200) |
| 144 | +out_frame <- as.parframe(out_frame) |
| 145 | +plotValues(out_frame) # Show "Waterfall" plot |
| 146 | +plotPars(out_frame) # Show parameter plot |
| 147 | +bestfit <- as.parvec(out_frame) |
| 148 | + |
| 149 | + |
| 150 | +# Plot predictions along data |
| 151 | +plot((g * x * p)(times, bestfit), data) |
| 152 | + |
| 153 | +# Plot sensis |
| 154 | +plot(getDerivs((g * x * p)(times, bestfit))) |
| 155 | + |
| 156 | +# Calculate Parameter Profiles and plot different contributions (for identifiablility only "data" is of interest) |
| 157 | +profiles <- profile(obj, bestfit, names(bestfit), method = "optimize", cores = 12) |
| 158 | +plotProfile(profiles) |
| 159 | +plotProfile(profiles,mode %in% c("data", "prior")) |
| 160 | +plotPaths(profiles, whichPar = "TCA_cana") |
| 161 | +plotPaths(profiles, whichPar = "export_cana") |
| 162 | +plotPaths(profiles, whichPar = "s") |
| 163 | + |
| 164 | +# Tighten model assumptions with steady state constraint |
| 165 | +pSS <- NULL |
| 166 | +equations <- getEquations(p) # getEquations retrieves the "trafo" from p |
| 167 | +conditions <- names(equations) |
| 168 | + |
| 169 | +# Set up steady state constraint |
| 170 | +for (n in conditions) { |
| 171 | + equations[[n]]["TCA_cana"] <- "exp(log(10)*export_cana)*exp(log(10)*TCA_cell)/exp(log(10)*reflux)" |
| 172 | + pSS <- pSS + P(equations[[n]], condition = n) |
| 173 | +} |
| 174 | +outerpars <- getParameters(pSS) |
| 175 | +pouter <- structure(rep(-1, length(outerpars)), names = outerpars) |
| 176 | +plot((g*x*pSS)(times, pouter),data) |
| 177 | + |
| 178 | +# Objective function |
| 179 | +obj <- normL2(data, g * x * pSS, times = times, attr.name = "data") + constraintL2(pouter, sigma = 5, attr.name = "prior") |
| 180 | + |
| 181 | +# Fit 50 times again |
| 182 | +out_frame <- mstrust(obj,pouter,studyname = "bamodel", cores=10,fits=50) |
| 183 | +out_frame <- as.parframe(out_frame) |
| 184 | +plotValues(out_frame) # Show "Waterfall" plot |
| 185 | +plotPars(out_frame) # Show parameter plot |
| 186 | +bestfit <- as.parvec(out_frame) |
| 187 | +plot((g * x * pSS)(times, bestfit), data) |
| 188 | + |
| 189 | + |
| 190 | +# Calculate Parameter Profiles |
| 191 | +profiles <- profile(obj, bestfit, names(bestfit), cores = 10, limits = c(lower = -10, upper = 10), |
| 192 | + stepControl = list(stepsize = 1e-4, min = 1e-4, max = Inf, atol = 1e-2, rtol = 1e-2, limit = 100, stop = "data")) |
| 193 | +plotProfile(profiles,mode %in% c("data", "prior")) |
| 194 | +plotPaths(profiles, whichPar = "s") |
| 195 | + |
| 196 | + |
| 197 | +# s and initial value TCA_cell render model structurally non identifiable |
| 198 | +# --> fix s to 1 (on log scale to 0) |
| 199 | + |
| 200 | +trafo <- getEquations(pSS) |
| 201 | +trafo <- repar("x~0", x = "s", trafo) |
| 202 | +p <- P(trafo) |
| 203 | + |
| 204 | + |
| 205 | +# Objective function |
| 206 | +outerpars <- getParameters(p) |
| 207 | +pouter <- structure(rep(-1, length(outerpars)), names = outerpars) |
| 208 | +obj <- normL2(data, g * x * p, times = times, attr.name = "data") + constraintL2(pouter, sigma = 20, attr.name = "prior") |
| 209 | + |
| 210 | +# Fit 50 times again |
| 211 | +out_frame <- mstrust(obj,pouter,studyname = "bamodel", cores=10,fits=50) |
| 212 | +out_frame <- as.parframe(out_frame) |
| 213 | +plotValues(out_frame) # Show "Waterfall" plot |
| 214 | +plotPars(out_frame) # Show parameter plot |
| 215 | +bestfit <- as.parvec(out_frame) |
| 216 | +plot((g * x * p)(times, bestfit), data) |
| 217 | + |
| 218 | +# remove the prior |
| 219 | +obj <- normL2(data, g * x * p, times = times, attr.name = "data") |
| 220 | + |
| 221 | +# refit |
| 222 | +refit <- trust(obj, bestfit, rinit = 1, rmax = 10) |
| 223 | +plot((g * x * p)(times, refit$argument), data) |
| 224 | + |
| 225 | +profiles <- profile(obj, refit$argument, names(refit$argument), cores = 10, limits = c(lower = -5, upper = 5), method = "optimize", |
| 226 | + stepControl = list(stop = "data")) |
| 227 | +plotProfile(profiles, mode == "data") |
| 228 | +plotPaths(profiles, whichPar = "reflux_open") |
| 229 | + |
| 230 | +# Note: The parameter "reflux_open" is still practical non-identifiable |
| 231 | +# The profile is open to the left -> A possible reduction of the model would be the assumption of an immediate reflux, i.e. the limit case reflux_open -> infinity |
| 232 | +# An pragmatic but ugly way to circumvent the reformulation of the ODE system is to fix the reflux_open parameter to a high value. E.g. 1e3 |
| 233 | + |
| 234 | +# One could also check the models ability to produce reliable predictions, by the calculation of prediction uncertainty with profile likelihood |
| 235 | +# The calculation of prediction confidence intervals is done at next |
| 236 | + |
| 237 | +## Prediction uncertainty taken from validation profile -------------------------------------------------------------------------- |
| 238 | + |
| 239 | +# choose sigma below 1 percent of the prediction in order to pull the prediction strongly towards d1 |
| 240 | +obj.validation <- normL2(data, g * x * p, times = times, attr.name = "data") + |
| 241 | + datapointL2(name = "TCA_cell", time = 10, value = "v", sigma = 1, attr.name = "validation", condition = "closed") |
| 242 | + |
| 243 | +# If sigma is not known, and you therefore decide to calculate prediction confidence intervals, just choose a very small sigma, in order to "pull strongly" on the trajectory |
| 244 | + |
| 245 | +# refit |
| 246 | +myfit <- trust(obj.validation, parinit = c(v = 180, bestfit), rinit = 1, rmax = 10, iterlim = 1000) |
| 247 | + |
| 248 | +# Calculate profile |
| 249 | +validation_profile <- profile(obj.validation, myfit$argument, "v", cores = 4, method = "optimize", |
| 250 | + optControl = list(rinit = .1, rmax = 10, iterlim = 100)) |
| 251 | + |
| 252 | + |
| 253 | +# plotProfile(validation_profile) # This also plos the prediction colums, which is a bug in the code. |
| 254 | +plotProfile(validation_profile, mode %in% c("validation", "data")) # Plots only the two contributions validation and data, along with the sum (total) |
| 255 | +# Is the contribution of validation small?? # If yes: The total aligns with a prediction profile |
| 256 | + |
| 257 | +# Confidence Interval of the prediction |
| 258 | +confint(validation_profile, val.column = "value") |
| 259 | + |
| 260 | + |
| 261 | + |
| 262 | +## Prediction band (prediction uncertainty for several time points) -------------------------------------------------------------- |
| 263 | +# Here we calculate a prediction CI for different timepoints. In the end we interpolate to a "prediction band" |
| 264 | +prediction_band <- do.call(rbind, lapply(seq(10, 50, 10), function(t) { |
| 265 | + |
| 266 | + cat("Computing prediction profile for t =", t, "\n") |
| 267 | + |
| 268 | + obj.validation <- normL2(data, g * x * p, times = times, attr.name = "data") + |
| 269 | + datapointL2(name = "TCA_cell", time = t, value = "v", sigma = 1, attr.name = "validation", condition = "closed") |
| 270 | + |
| 271 | + refit <- trust(obj.validation, parinit = c(v = 180, bestfit), rinit = 1, rmax = 10, iterlim = 1000) |
| 272 | + |
| 273 | + profile_prediction <- profile(obj.validation, myfit$argument, "v", cores = 4, method = "optimize") |
| 274 | + |
| 275 | + d1 <- confint(profile_prediction, val.column = "value") |
| 276 | + |
| 277 | + # Output |
| 278 | + data.frame(time = t, condition = "closed", name = "TCA_cell", d1[-1]) |
| 279 | + |
| 280 | +})) |
| 281 | + |
| 282 | +prediction <- (g * x * p)(times, refit$argument) %>% |
| 283 | + as.data.frame() |
| 284 | + |
| 285 | + |
| 286 | +prediction_band_spline <- data.frame( |
| 287 | + time = prediction$time[prediction$time>=10], |
| 288 | + value = prediction$value[prediction$time>=10], |
| 289 | + condition = "closed", |
| 290 | + name = "TCA_cell", |
| 291 | + lower = spline(prediction_band$time, prediction_band$lower, xout = prediction$time[prediction$time>=10])$y, |
| 292 | + upper = spline(prediction_band$time, prediction_band$upper, xout = prediction$time[prediction$time>=10])$y |
| 293 | +) |
| 294 | + |
| 295 | +# Create the ggplot |
| 296 | +ggplot(prediction, aes(x = time, y = value, color = condition)) + |
| 297 | + geom_line() + # Line connecting the points for each condition |
| 298 | + geom_ribbon(data = prediction_band_spline, aes(x = time, ymin = lower, ymax = upper, fill = condition), |
| 299 | + lty = 0, alpha = .3, show.legend = F) + # Show ribbon in the legend |
| 300 | + geom_point(data = prediction_band, aes(x = time, y = lower, color = condition), shape = 4, show.legend = F) + |
| 301 | + geom_point(data = prediction_band, aes(x = time, y = upper, color = condition), shape = 4, show.legend = F) + |
| 302 | + facet_wrap(~ name, scales = "free_y") + # Facet by 'name' column |
| 303 | + labs( |
| 304 | + x = "Time", |
| 305 | + y = "Value", |
| 306 | + color = "Condition" |
| 307 | + ) + |
| 308 | + dMod::theme_dMod() + # Apply dMod theme |
| 309 | + dMod::scale_color_dMod() + # Apply dMod color scale to lines |
| 310 | + dMod::scale_fill_dMod() # Apply the same color scale to the fill |
| 311 | + |
| 312 | + |
| 313 | +# ## Alternative implementation of the Steady state via implicit parameter transformation of the steady state ----------------------------------------------- |
| 314 | +# |
| 315 | +# # Redefine reactions in order to control the standard and open condition by events |
| 316 | +# reactions <- eqnlist() %>% |
| 317 | +# addReaction("TCA_buffer", "TCA_cell", rate = "import*TCA_buffer", description = "Uptake")%>% |
| 318 | +# addReaction("TCA_cell", "TCA_buffer", rate = "export_sinus*TCA_cell", description = "Sinusoidal export")%>% |
| 319 | +# addReaction("TCA_cell", "TCA_cana", rate = "export_cana*TCA_cell", description = "Canalicular export")%>% |
| 320 | +# addReaction("TCA_cana", "TCA_buffer", rate = "(reflux*(1-switch) + reflux_open*switch)*TCA_cana", description = "Reflux into the buffer")%>% |
| 321 | +# addReaction("0", "switch", rate = "0", description = "Create a switch") |
| 322 | +# |
| 323 | +# events <- NULL |
| 324 | +# events <- addEvent(events, var = "TCA_buffer", time = 0, value = 0) |
| 325 | +# events <- addEvent(events, var = "switch" , time = 0, value = "OnOff") |
| 326 | +# mymodel <- odemodel(reactions, modelname = "bamodel2", events = events) |
| 327 | +# x <- Xs(mymodel) |
| 328 | +# |
| 329 | +# |
| 330 | +# |
| 331 | +# # Replace one reaction with a analytical expression for the conserved quantity: TCR_tot |
| 332 | +# f <- as.eqnvec(reactions)[c("TCA_buffer", "TCA_cana", "TCA_cell")] |
| 333 | +# f["TCA_cell"] <- "TCA_buffer + TCA_cana + TCA_cell - TCA_tot" |
| 334 | +# pSS <- P(f, method = "implicit", compile = TRUE, modelname = "pfn") |
| 335 | +# |
| 336 | +# observables <- eqnvec(buffer = "s*TCA_buffer", cellular = "s*(TCA_cana + TCA_cell)") |
| 337 | +# |
| 338 | +# innerpars <- unique(c(getParameters(mymodel), getSymbols(observables), getSymbols(f))) |
| 339 | +# trafo <- repar("x~x" , x = innerpars) |
| 340 | +# trafo <- repar("x~0" , x = reactions$states, trafo) |
| 341 | +# |
| 342 | +# trafo <- repar("x~exp(log(10)*x)", x = setdiff(innerpars, "OnOff"), trafo) |
| 343 | +# p <- P(repar("OnOff~0", trafo), condition = "closed") + P(repar("OnOff~1", trafo), condition = "open") |
| 344 | +# g <- Y(observables, f = x, compile = TRUE, modelname = "obsfn2") |
| 345 | +# |
0 commit comments