Acute 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() 
currentrmdfile = "acutevirusir_documentation.Rmd" #for debugging
appsettings = get_settings(currentrmdfile,appdocdir,packagename)

Overview {#shinytab1}

This app allows exploration of an acute 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.

The model used here is a variation of the one introduced in the Basic Virus Model app. If you haven't done so, check out and explore that app first.

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:

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 & = - b U V \ \dot I & = b U V - 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 F - 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 infection dynamics 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 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 50 days; produce plots with and without log-scales. You should get a single, acute infection with virus and infected cells rising at first and then declining. At the end you should be left with around 11069 uninfected cells and essentially no infected cells or virus."
nrec = 1 # number of items to record
out_records = c("Number of infected cells at the peak of infection")
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 = "In the absence of the immune response, this model is the same as the one covered in the first part of the _Basic Virus_ app. Convince yourself that you can get at most one rise, then decline of virus, and confirm that this acute infection increases as you increase virus production and infection rates, and that it decreases as you increase infected and virus death rates. \n Double the virus production and infection rate parameters (compared to the previous task), and halve the lifespans of the infected cells and virus (remember the relation between durations, such as lifespan, and rates, such as death rates.) Run the simulation. Compare the number of uninfected cells remaining at the end with what you found for task 1. If it's not clear to you why you get what you find, revisit the __Basic Virus__ app and especially the task discussing the reproductive number."
nrec = 1 # number of items to record
out_records = c("Remaining uninfected cells at the end")
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~_ = 0.01, _d~T~_ = 1 and _k~T~_ = 0.01.  Run the simulation and convince yourself that in the absence of the innate response, the adaptive response is not triggered, and the resulting infection thus looks like the one you saw in the first task. If it is unclear to you why the adaptive response is not triggered, look at the model. You'll see that induction/growth of the adaptive response depends on the innate response."
nrec = 1 # number of items to record
out_records = c("Maximum value of infected cells")
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 = "The previous task showed that in the absence of the innate response, the adaptive response won't kick in. The reverse is not true. Set everything back as in task 1. Then set the starting value for _F_ to 1, _r~F~_ = 10, _d~F~_ = 1 and _k~F~_ = 0. With these settings, the innate response is activated, but has no impact yet on the virus kinetics. You should see the innate response come up and decay, but the same virus and infected cell trajectories as for task 1. Now, turn on the action of the innate response by setting _k~F~_ = 10^-5^. You should notice that the innate response reduces the virus load curve and this leads to fewer total cells that get infected."
out_records = c("Remaining uninfected cells at the end with innnate action turned on")
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 5
#########################
tid = tid + 1
tasktext = "Finally, we'll add the adaptive response. Keep settings as in the previous task. Then set the starting value for _T_ to 1, , set _r~T~_ = 2 x 10^-5^, _d~T~_ = 0.1 and _k~T~_ = 0. Run the simulation. You should see the adaptive response come up, but since we set its killing rate to 0, it does not yet impact the virus or cell kinetics. Let's change that by setting _k~T~_ = 10^-4^. You should now see both the innate and adaptive response being induced, and both act to reduce the impact of the infection, with a further reduction in virus load and even fewer infected cells at the end. You will also see that the adaptive response is lower now, since the faster clearance of virus and thus reduction of the innate response leads to a reduced adaptive response. This is an example of the complex interactions one can get with these kind of models, even fairly simple ones like this."
out_records = c("Remaining uninfected cells at the end with T-cell killing turned on")
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 = "Continue to explore how different strengths of innate and adaptive response kinetics impact the virus and infected cell dynamics. The kinetics of those immune responses is determined by their induction rates _r~F~_ and _r~T~_ and decay rates _d~F~_ and _d~T~_. Then explore further how immune response action, as represented by the parameters _k~F~_ and _k~T~_, impact the outcomes. Finally, explore how all of this changes as you change some of the parameters associated with virus and cell dynamics. You might notice that the adaptive response is very sensitive to changes in _r~T~_, probably more so than makes biological sense. This suggests that a different formulation of the model might possibly be better. Some alternatives are explored and discussed in the __Extended Virus and Immune Response__ and __Model Variant Exploration__ apps."
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.

This model is based on one of the models discussed in [@li14]. See also for instance [@beauchemin11; @smith11] and references in the other apps in this section.

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.