Chronic Virus Infection and Immune Response Model

#load various variable definitions that are the same for each app
source('startup_script.R')
currentrmdfile = knitr::current_input() 
appsettings = get_settings(currentrmdfile,appdocdir,packagename)

Overview {#shinytab1}

This app allows exploration of a chronic virus infection model, with compartments for uninfected cells, infected cells and (free) virus. The model also includes abstract versions of the innate and adaptive immune response. Read about the model in The Model tab. Then, work through the tasks described in the What To Do tab.

Learning Objectives

The Model {#shinytab2}

Model Overview

This model consists of 5 compartments and can capture some of the basic dynamics of viral infections and the ensuing immune response. In this model, we track the following entities, by assigning each to a compartment:

In addition to specifying the compartments of a model, we need to specify the dynamics determining the changes for each compartment. Broadly speaking, there are processes that increase the numbers in a given compartment/stage, and processes that lead to a reduction. Those processes are sometimes called in-flows and out-flows.

For our system, we specify the following processes/flows:

Note that the choice of growth dynamics for the adaptive response is slightly different from the acute virus model. There, we assumed the growth was proportional to the innate response F. Here, we assume it is driven by virus/antigen load, V. Both assumptions are potentially reasonable approximations. In reality, it is likely a complex interaction between virus/antigen load and innate response that drives adaptive responses. Trying to model this in detail is beyond the goal of this app. We want to keep it reasonably simple here so we can get some intuition in these kinds of models. For any real problem, you need to decide which components and processes to include and how to model them. The idea of modeling the system in different ways is explored and discussed in the Model Variant Exploration apps.

Model Diagram

The diagram illustrating this compartmental model is shown in the figure.

knitr::include_graphics(path = paste0("../media/",appsettings$modelfigname))

Model Equations

Implementing this model as a continuous-time, deterministic model leads to the following set of ordinary differential equations.

\begin{aligned} \dot U & = n - d_U U - b U V \ \dot I & = bUV - d_I I - k_T T I \ \dot V & = \frac{p}{1+k_F F}I - d_V V - gb UV \ \dot F & = r_F I - d_F F \ \dot T & = r_T T V - d_T T \end{aligned}

What To Do {#shinytab3}

The tasks below are described in a way that assumes everything is in units of days (rate parameters, therefore, have units of inverse days). If any quantity is not given in those units, you need to convert it first (e.g. if it says a week, you need to convert it to 7 days).

#this is the running counter for the records which starts at 1 
rc=1

#empty object, will hold all outcomes
alloutcomes = NULL

#########################
# Task 1
#########################
tid = 1
tasktext = "Let's start by considering a chronic viral infection in the absence of the immune response. 
Set the initial conditions to 10^5^ uninfected cells, no infected cells, and 10 virus particles. Assume also that infected cells have an average life-span of 1 day, and virus has a life-span of 6 hours (remember that the inverse of the lifespan is the rate of death, and make sure you convert to the right units). Set that the virus production by an infected cell is 100 virions per day and that the rate at which new cells become infected is 10^-6^. Assume there is no need to do any unit adjustment/conversion (i.e. the value of that parameter is 1). Set the birth rate of uninfected cells to 10^4^, and the life span od the uninfected cells to 10 days.
Set the starting values for the immune response variables _F_ and _T_ to 0, and also set the two immune response induction rates to 0. With those choices, the values for the other immune response related parameters do not matter. If you want, you can also set them all to 0. \nRun the simulation for 200 days. You should get one major rise in virus, followed by a few minor peaks and then settling down to a steady state where the virus levels are at around 147500."
nrec = 2 # number of items to record
out_records = c("Number of uninfected cells at the end of the simulation", "Number of infected cells at the end of the simulation")
out_types = rep("Rounded_Integer",nrec)
out_notes = rep("Report the rounded integer",nrec)
outcomes = data.frame( TaskID = rep(tid,nrec),
                       TaskText = rep(tasktext,nrec),
                      RecordID = paste0('T',tid,'R',(1:nrec)),
                      Record = out_records, 
                      Type = out_types, 
                      Note = out_notes)
alloutcomes = rbind(alloutcomes,outcomes)
rc = rc + nrec #increment record counter by number of outcomes to record for this task 

#########################
# Task 2
#########################
tid = tid + 1
tasktext = "As you might expect, and as discussed in the __Basic Virus__ app, changing the parameters associated with the virus and cell kinetics can change the results. How a certain parameter impacts the steady/chronic level is not always intuitively obvious. Give that a quick try. Double the rate at which uninfected cells are produced, i.e. set _n = 2x10^4^_. One might expect that this should increase the number of uninfected cells at steady state. Run the simulation to determine that this is NOT the case, that instead the number of infected cells changes. Revisit the __Basic Virus__ app if it is unclear to you why this happens."
nrec = 2 # number of items to record
out_records = c("Number of uninfected cells at the end of the simulation", "Number of infected cells at the end of the simulation")
out_types = rep("Rounded_Integer",nrec)
out_notes = rep("Report the rounded integer",nrec)
outcomes = data.frame( TaskID = rep(tid,nrec),
                       TaskText = rep(tasktext,nrec),
                      RecordID = paste0('T',tid,'R',(1:nrec)),
                      Record = out_records, 
                      Type = out_types, 
                      Note = out_notes)
alloutcomes = rbind(alloutcomes,outcomes)
rc = rc + nrec #increment record counter by number of outcomes to record for this task 


#########################
# Task 3
#########################
tid = tid + 1
tasktext = "Now turn on the immune response. Set everything as you did for the first task. Then change the starting value for _T_ to 1, set _r~T~_ = 10^-6^, _d~T~_ = 0.01 and _k~T~_ = 0. Keep the innate response turned off. Run the simulation for 200 days. You might need to change the plot to a log y-axis so you can see all variables. You will see that - in contrast to the __Acute Virus__ model, the adaptive response is triggered. However, since we set its action to 0, it does not yet impact the virus and cell kinetics. 
Let's change that. Set _k~T~_ = 10^-5^. Run the simulation. You should see the adaptive response slowly coming in and clearing infected cells, which leads to a reduction in virus load and a recovery of the uninfected cells."
nrec = 2 # number of items to record
out_records = c("Number of uninfected cells at the end of the simulation, non-zero _k~T~_", "Number of infected cells at the end of the simulation, non-zero _k~T~_")
out_types = rep("Rounded_Integer",nrec)
out_notes = rep("Report the rounded integer",nrec)
outcomes = data.frame( TaskID = rep(tid,nrec),
                       TaskText = rep(tasktext,nrec),
                      RecordID = paste0('T',tid,'R',(1:nrec)),
                      Record = out_records, 
                      Type = out_types, 
                      Note = out_notes)
alloutcomes = rbind(alloutcomes,outcomes)
rc = rc + nrec #increment record counter by number of outcomes to record for this task 


#########################
# Task 4
#########################
tid = tid + 1
tasktext = "Explore how different strengths of the adaptive response impact the results. Keep all settings at the values you just had. Then run the model for _k~T~_ = 10^-2^, 10^-3^, 10^-4^,... all the way to 10^-12^ (that's 11 different values). Each time, run for 200 days and record final number of infected cells and virus. Then make two plots, both with the _k~T~_ values on the x-axis and the first with infected cell on the y-axis, the second with virus numbers on the y-axis. From these plots, determine (approximately) the values of _k~T~_ for which infected cell numbers are at 500, and the value at which virus load is at 20,000. Ideally, you would do this task writing a bit of R code to run a loop over the values and draw the plots. But of course you can also do it manually."
nrec = 1 # number of items to record
out_records = c("True or False: The value of _k~T~_ at which virus load is 20,000 is greater than 1.5 x 10^-10^")
out_types = rep("Logical",nrec)
out_notes = rep("Report True or False",nrec)
outcomes = data.frame( TaskID = rep(tid,nrec),
                       TaskText = rep(tasktext,nrec),
                      RecordID = paste0('T',tid,'R',(1:nrec)),
                      Record = out_records, 
                      Type = out_types, 
                      Note = out_notes)
alloutcomes = rbind(alloutcomes,outcomes)
rc = rc + nrec #increment record counter by number of outcomes to record for this task 


#########################
# Task 5
#########################
tid = tid + 1
tasktext = "Now let's add the innate response to the model. Keep everything as you just had it. That means _U = 10^5^_, _I = 0_, _V = 10_, _F = 0_, _T = 1_, _n = 10^4^_, _d~U~ = 0.1_, _d~I~ = 1_, _d~V~ = 4_, _b = 10^-6^_, _p = 100_, _g = 1_, _r~T~_ = 10^-6^, _d~T~_ = 0.01 and _k~T~_ = 10^-5^. The set _F = 1_, _r~F~_ = 10, _d~F~_ = 1. Start with _k~F~_ = 0 and run the simulation for 200 days. You see the inate response coming up, but since it doesn't have an effect yet, the virus and cell kinetics doesn't change from the results you got above in the absence of the innate response. Now re-run with _k~F~_ = 10^-5^. You should see that the peak virus load is lower. Maybe surprisingly, the virus load at the end is actually higher. The reason is that the reduced virus load leads to a slower induction of the adaptive response, and the weaker adaptive response in turn does not clear infected cells as well (by day 200, if you run it longer the adaptive response will do its job)."
nrec = 4 # number of items to record
out_records = c("Peak virus load with _k~F~_ = 0","Final virus load with with _k~F~_ = 0","Peak virus load with _k~F~_ = 10^-5^","Final virus load with with _k~F~_ = 10^-5^")
out_types = rep("Rounded_Integer",nrec)
out_notes = rep("Report the rounded integer",nrec)
outcomes = data.frame( TaskID = rep(tid,nrec),
                       TaskText = rep(tasktext,nrec),
                      RecordID = paste0('T',tid,'R',(1:nrec)),
                      Record = out_records, 
                      Type = out_types, 
                      Note = out_notes)
alloutcomes = rbind(alloutcomes,outcomes)
rc = rc + nrec #increment record counter by number of outcomes to record for this task 


#########################
# Task 6
#########################
tid = tid + 1
tasktext = "We finish with one more systematic exploration. Keep all settings as in the previous task, but change simulation time to 300 days. Run the model for these _k~F~_ values: 10^-8^, 10^-7^, ... , 10^-3^ (6 values). For each value, record peak and final virus load. Repeat all of these runs, but now with _r~T~_ = 10^-7^. Make a plot with the _k~F~_ values on the x-axis and the peak virus values on the y-axis, for both levels of _r~T~_. Then make a second plot, now with final virus load on the y-axis."
nrec = 1 # number of items to record
out_records = c("True or False: The final virus load decreases monotonically as _k~F~_ increases, for both values of _r~T~_")
out_types = rep("Logical",nrec)
out_notes = rep("Report True or False",nrec)
outcomes = data.frame( TaskID = rep(tid,nrec),
                       TaskText = rep(tasktext,nrec),
                      RecordID = paste0('T',tid,'R',(1:nrec)),
                      Record = out_records, 
                      Type = out_types, 
                      Note = out_notes)
alloutcomes = rbind(alloutcomes,outcomes)
rc = rc + nrec #increment record counter by number of outcomes to record for this task 


#########################
# Task 7
#########################
tid = tid + 1
tasktext = "Continue to explore how different strengths of innate and adaptive response kinetics impact the virus and infected cell dynamics, both during the acute and the chronic phase."
nrec = 1 # number of items to record
out_records = c("Nothing")
out_types = rep("None",nrec)
out_notes = rep("",nrec)
outcomes = data.frame( TaskID = rep(tid,nrec),
                       TaskText = rep(tasktext,nrec),
                      RecordID = paste0('T',tid,'R',(1:nrec)),
                      Record = out_records, 
                      Type = out_types, 
                      Note = out_notes)
alloutcomes = rbind(alloutcomes,outcomes)
rc = rc + nrec #increment record counter by number of outcomes to record for this task 
#save the fully filled task table to a tsv file
alloutcomes$QuizID = paste0(packagename,"_",appsettings$appid)
alloutcomes$AppTitle = appsettings$apptitle
alloutcomes$AppID = appsettings$appid
#remove a few variables from the data frame
savedoutcomes <- dplyr::select(alloutcomes,QuizID,AppID,AppTitle,TaskID,TaskText,RecordID,Record,Type,Note)     
write.table(savedoutcomes, paste0(appsettings$appid,"_tasktable.tsv"), append = FALSE, sep = "\t", row.names = F, col.names = TRUE)
# Take all the text stored in the table and print the tasks and items to record
write_tasktext(alloutcomes)

Further Information {#shinytab4}

This app (and all others) are structured such that the Shiny part (the graphical interface you see and the server-side function that goes with it) calls an underlying R script (or several) which runs the simulation for the model of interest and returns the results.

For this app, the underlying function running the simulation is called r appsettings$simfunction. You can call them directly, without going through the shiny app. Use the help() command for more information on how to use the functions directly. If you go that route, you need to use the results returned from this function and produce useful output (such as a plot) yourself.

You can also download all simulator functions and modify them for your own purposes. Of course to modify these functions, you'll need to do some coding. For examples on using the simulators directly and how to modify them, read the package vignette by typing vignette('DSAIRM') into the R console.

If you want to learn a bit more about these kinds of models applied to acute viral infections, specifically influenza, see e.g. [@beauchemin11; @smith11]. A few examples of these kinds of models applied to chronic viral infections, see e.g. [@guedj10; @chatterjee12; @perelson13].

References



Try the DSAIRM package in your browser

Any scripts or data that you put into this service are public.

DSAIRM documentation built on Aug. 24, 2023, 1:07 a.m.