{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# MuMoT Demonstration Notebook: SEIR-Derived Epidemiological Models of COVID-19 and Social Distancing\n", "\n", "## Multiscale Modelling Tool \n", "\n", "*James A. R. Marshall, Department of Computer Science, University of Sheffield*\n", "\n", "# Introduction\n", " \n", "This notebook aims to help explain the possible epidemiology of and effects of different public interventions on the COVID-19 novel coronavirus infectious disease, caused by the SARS-CoV-2 virus (WHO, 2020). Models are presented to show how different health policies may impact on the progression of the disease through a population. The models are simple 'toy' models for illustrative purposes, although model parameters are taken from the literature on the disease.\n", "\n", "_**If you are a non-specialist, you may simply want to go to the `Kernel` menu above, select `Restart & Run All`, then skip to the interactive plots and use these, along with reading their accompanying bold text.**_" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import mumot" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Model Definitions\n", "\n", "Our model is adapted from the standard SEIR model (Susceptiple, Exposed, Infectious, Recovered). The SEIR model is a model used to represent infectious diseases with a latent period following infection, before an individual becomes infectious, and has been used in modelling COVID-19 (e.g.Wu et al., 2020). However, since COVID-19 may exhibit significant presymptomatic infectiousness (Anderson et al., 2020; Du et al.2020, Tindal et al., 2020), we also include an infection process from members of the $E$ class, which is not usual in SEIR models; comparatively few models of COVID-19 to date have explicitly included infection by asymptomatic individuals (see Cao et al. (2020) and Li et al.(2020) for exceptions). Since asymptomatic individuals apparently can also recover directly without becoming symptomatic, we also adapt the SEIR model to allow direct recovery from the exposed class $E$. For simplicity, we assume the same infection rate $r$ regardless of whether a person is in $E$ or $I$.\n", "\n", "For COVID-19 infection rates are assumed to be higher than recovery rates.\n", "\n", "For health systems, the key population to track is the infectious population $I$, which in our model represents the symptomatic members of the population, since these are the ones that will require healthcare resources such as medicines, medical callouts, or hospitalisation. _Note that this model does not explicitly represent deaths from infection - all population members eventually recover, which is clearly not the case for COVID-19_\n", "\n", "Thus our model is defined as follows:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%model\n", "$\n", "S + I -> I + E: r * s\n", "S + E -> E + E: r * (1 - s)\n", "E -> I: a\n", "E -> R: g\n", "I -> R: g\n", "$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fullmodel = mumot.parseModel(In[-2])\n", "finitemodel = fullmodel.substitute('S = N - I - E - R')\n", "finitemodel.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Results\n", "In the following, we analyse the models using both infinite-population ODEs, and finite population spatial stochastic simulations. _The latter are particularly good for allowing us to vary *social distancing* and see the effects on the infection's progress_.\n", "\n", "## Social Distancing Across the Population\n", "We parameterise the model to approximate the characteristics of COVID-19; the purpose of this demonstration is to show the effects of social distancing, so exact parameterisation is not required but could be found from the literature (see sample literature in References). Here we choose $r=2.5$ and $a=g=1/2$; estimates of infectiousness vary but are higher than previous comparable diseases (see Liu et al., 2020, for a review) estimates of asymptomatic transmissions share of total infections vary between being a minority (Du et al., 2020) and, potentially, a majority (Tindal et al., 2020; Li et al., 2020); we assume infectiousness for members of $E$ is half that of members in $I$ ($s=2/3$) (Li et al., 2020). We then present a controller in which there is a single slider to change the degree of social isolation. This enables us to easily compare the effects of no social isolation (a 'well-mixed' population, left) with social isolation (a spatial, partially connected population, right)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_**You can see how social distancing affects the spread of the disease within the population by looking inside the population as individuals mingle and interact. Open the 'Advanced options' tab and vary the interaction range between 0 (full social distancing) and 1 (no social distancing), then observe the effect on the population. Recall that green individuals are the ones who may need hospital treatment, and the red individuals are recovered. Implementing social distancing means the virus has fewer opportunities to jump between individuals, so doesn't spread as fast.**_" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "agentcont1 = finitemodel.multiagent(ylab = 'cases', initialState = {'E': 0.05, 'I': 0.0, 'R': 0.0, 'S': 0.95}, initWidgets={'netParam':[1.0, 0, 1.0, 0.05]}, maxTime = 10.0, timestepSize = 0.25, randomSeed = 731529356, netType = 'dynamic', motionCorrelatedness = 0.5, particleSpeed = 0.01, showTrace = False, showInteractions = False, visualisationType = 'graph', plotProportions = True, realtimePlot = True, runs = 1, aggregateResults = True, bookmark = False, silent = False, params = [('s', 2/3),('a', 0.5), ('g', 0.5), ('r', 2.0), ('plotLimits', 1), ('systemSize', 100.0)])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Open the 'Advanced options' tab again and vary the interaction range between 0 (full social distancing) and 1 (no social distancing), then observe the effect on a sample infection trajectory in the right hand plot, compared to the left hand reference plot (no social distancing). The plot shows the fractions of the total population that are in the different disease states, indicated by the figure lengends. Note that social distancing not only reduces the peak number of cases needing medical attention, but also the total number of infections.\n", "\n", "_**As you vary the interaction range slider between full social contact (1.0) and no social contact (0.0) you should observe a threshold effect at around 0.10, where the progress of the disease is drastically reduced; the maximum proportion of cases at any point in time (green line) reduces.**_" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "multicont1 = mumot.MuMoTmultiController(\n", " [finitemodel.integrate(showStateVars=['E', 'I', 'R'], ylab = 'cases', initialState = {'E': 0.05, 'I': 0.0, 'R': 0.0, 'S': 0.95}, maxTime = 10.0, plotProportions = True, silent = True, conserved = True, bookmark = False),\n", " finitemodel.multiagent(ylab = 'cases', initialState = {'E': 0.05, 'I': 0.0, 'R': 0.0, 'S': 0.95}, initWidgets={'netParam':[1.0, 0, 1.0, 0.05]}, maxTime = 10.0, timestepSize = 0.25, randomSeed = 731529356, netType = 'dynamic', motionCorrelatedness = 0.5, particleSpeed = 0.01, showTrace = False, showInteractions = False, visualisationType = 'evo', plotProportions = True, realtimePlot = False, runs = 1, aggregateResults = True, silent = True, bookmark = False)], params = [('s', 2/3),('a', 0.5), ('g', 0.5), ('r', 2.5), ('plotLimits', 1), ('systemSize', 100.0)],\n", " choose_yrange = [0, 1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Social Distancing vs Isolating Symptomatic Cases\n", "\n", "We now adapt our model to examine the possible relative effectiveness of social distancing, compared to isolating only symptomatic individuals. The basic infectiousness of the disease $r$ can be varied, as can the share of infections generated by symptomatic individuals $I$ ($s$). We make a new model in which symptomatic individuals are isolated so reduce their infectivity ten-fold, and introduce a social distancing parameter $d$ that scales contacts between asymptomatic individuals ($E$) and susceptible individuals ($S$). For this scenario we now assume asymptomatic infectiousness is twice that of symptomatic (cf. Tindal et al., 2020; Li et al., 2020), $s=1/3$." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%model\n", "$\n", "S + I -> I + E: r * s / 10\n", "S + E -> E + E: r * d * (1 - s)\n", "E -> I: a\n", "E -> R: g\n", "I -> R: g\n", "$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_**Vary the social distancing parameter $d$ to see the difference that social distancing by asymptotic carriers makes (righthand plot), on top of isolation only of symptomatic cases (lefthand plot). You can also vary the infectiousness of the disease ($r$) and proportion of infections due to symptomatic carriers ($s$), since there is some uncertainty around these.**_" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "compmodel1 = mumot.parseModel(In[-2]).substitute('S = N - I - E - R')\n", "compmodel1.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "multicont2 = mumot.MuMoTmultiController(\n", " [compmodel1.integrate(showStateVars=['E', 'I', 'R'], ylab = 'cases', initialState = {'E': 0.05, 'I': 0.0, 'R': 0.0, 'S': 0.95}, maxTime = 10.0, choose_yrange = [0, 1], plotProportions = True, silent = True, conserved = True, bookmark = False, params = [('d', 1)]),\n", " compmodel1.integrate(showStateVars=['E', 'I', 'R'], ylab = 'cases', initialState = {'E': 0.05, 'I': 0.0, 'R': 0.0, 'S': 0.95}, maxTime = 10.0, choose_yrange = [0, 1], plotProportions = True, silent = True, conserved = True, bookmark = False)],\n", " initWidgets={'r':[2.5, 0, 4, 0.1],'s':[0.35, 0, 1, 0.05], 'd':[1, 0.5, 1, 0.05]}, params = [('a', 0.5), ('g', 0.5), ('plotLimits', 1), ('systemSize', 10)])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Whole-Population Social Distancing vs Distancing for High Risk Groups\n", "\n", "Similarly to examining the effects of isolation only on symptomatic individuals above, Benjamin Kerr (2020) and Omar Cornejo (2020) constructed a model of social distancing for different risk groups. Here we adapt our modified SEIR model to include their concept of high and low risk groups. The basic rate parameters for the disease are the same for each, but social distancing factors can be applied to each independently." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%model\n", "$\n", "S_h + E_h -> E_h + E_h: r * (1 - s) * d_h\n", "S_l + E_h -> E_l + E_h: r * (1 - s) * d_l\n", "S_h + E_l -> E_h + E_l: r * (1 - s) * d_h\n", "S_l + E_l -> E_l + E_l: r * (1 - s) * d_l\n", "S_h + I_h -> E_h + I_h: r * s * d_h\n", "S_l + I_h -> E_l + I_h: r * s * d_l\n", "S_h + I_l -> E_h + I_l: r * s * d_h\n", "S_l + I_l -> E_l + I_l: r * s * d_l\n", "E_l -> I_l: a\n", "E_h -> I_h: a\n", "E_l -> R: g\n", "E_h -> R: g\n", "I_l -> R: g\n", "I_h -> R: g\n", "$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "compmodel2 = mumot.parseModel(In[-2]).substitute('S = N - I_h - I_l - E_h - E_l - R')\n", "compmodel2.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The model is paramaterised as follows: The susceptible population is 85:15 low risk:high risk, and initial carriers are equally divided between the low risk and high risk groups. The initial 5% of infected individuals are half low risk, half high risk. As above, infection rates of symptomatic ($r$) and asymptomatic ($r * s$) can be manipulated." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_**In the following plot you can manipulate the extent of social distancing for the high-risk ($d_h$) and low-risk ($d_l$) groups, from 1 (no social distancing for that group) to 0 (full social isolation for that group). The plots show (top-left) disease progression with no social distancing measures on any group; (top-right) progression with social distancing only on the high-risk group (bottom-left) progression with social distancing only on the low-risk group; (bottom-right) progression with social distancing only on the high-risk group. Low risk (dashed cyan) and high risk (solid red) infected populations are shown**_" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "multicont2 = mumot.MuMoTmultiController(\n", " [compmodel2.integrate(showStateVars=['I_h', 'I_l'], ylab = 'cases', initialState = {'E_h': 0.025, 'I_h': 0.0, 'E_l': 0.025, 'I_l': 0.0, 'R': 0.0, 'S_h': 0.15, 'S_l': 0.8}, maxTime = 10.0, choose_yrange = [0, 0.3], plotProportions = True, silent = True, conserved = True, bookmark = False, params = [('d_h', 1), ('d_l', 1)]),\n", " compmodel2.integrate(showStateVars=['I_h', 'I_l'], ylab = 'cases', initialState = {'E_h': 0.025, 'I_h': 0.0, 'E_l': 0.025, 'I_l': 0.0, 'R': 0.0, 'S_h': 0.15, 'S_l': 0.8}, maxTime = 10.0, choose_yrange = [0, 0.3], plotProportions = True, silent = True, conserved = True, bookmark = False, params = [('d_l', 1)]),\n", " compmodel2.integrate(showStateVars=['I_h', 'I_l'], ylab = 'cases', initialState = {'E_h': 0.025, 'I_h': 0.0, 'E_l': 0.025, 'I_l': 0.0, 'R': 0.0, 'S_h': 0.15, 'S_l': 0.8}, maxTime = 10.0, choose_yrange = [0, 0.3], plotProportions = True, silent = True, conserved = True, bookmark = False, params = [('d_h', 1)]),\n", " compmodel2.integrate(showStateVars=['I_h', 'I_l'], ylab = 'cases', initialState = {'E_h': 0.025, 'I_h': 0.0, 'E_l': 0.025, 'I_l': 0.0, 'R': 0.0, 'S_h': 0.15, 'S_l': 0.8}, maxTime = 10.0, choose_yrange = [0, 0.3], plotProportions = True, silent = True, conserved = True, bookmark = False)],\n", " initWidgets={'r':[2.5, 0, 4, 0.1],'s':[0.35, 0, 1, 0.05], 'd_{h}':[0.25, 0, 1, 0.05], 'd_{l}':[0.25, 0, 1, 0.05]}, params = [('a', 0.5), ('g', 0.5), ('plotLimits', 1), ('systemSize', 10)])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Discussion\n", "_**The purpose of this notebook has been to highlight, using simple models that still capture key aspects of the disease, how various public health policy interventions might affect\n", "the spread of COVID-19. The key messages are:**_\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# References \n", "\n", "" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.6" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": true } }, "nbformat": 4, "nbformat_minor": 2 }