{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Pandemic

\n",
"\n",
"(Sethna, \"Entropy, Order Parameters, and Complexity\", ex. XXX)\n",
"\n",
"© 2020, James Sethna, all rights reserved."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"%pylab inline\n",
"from scipy import *"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Perhaps the most substantive contribution to public health provided\n",
"by physics is the application of statistical mechanics ideas to\n",
"model disease propagation. In this exercise, we shall introduce a\n",
"few categories of epidemiological models, discuss how they can inspire\n",
"and inform public health strategies (once adapted to real-world data),\n",
"and then study one model as a continuous phase transition. You should\n",
"leave this exercise empowered to think about the public health responses\n",
"and modeling of potential pandemics -- Ebola, SARS, and now COVID-19.\n",
"Perhaps a few of us will contribute to the field.\n",
"\n",
"Pandemics can undergo a phase transition. For diseases like\n",
"measles, a single contagious child in an environment where nobody is immune\n",
"will infect between twelve and eighteen people before recovering, depending\n",
"on details like population density. For influenza, this number is around\n",
"two to three. We define the 'basic reproduction number' $R_0$ to be\n",
"the ratio of infected people per contagious person in a fully susceptible\n",
"community: 12--18 for measles, 2--3 for influenza.\n",
"For a new pathogen, where nobody is immune, $R_0<1$ will\n",
"mean that an outbreak will eventually die out, and $R_0>1$ means that a large\n",
"initial outbreak will spread globally until reaching a significant fraction of\n",
"the entire population ($R_0>1$). Much effort is spent during a pandemic to\n",
"lower $R_0$ into the safe range.\n",
"\n",
"This transition is a continuous phase transition, with fluctuations on\n",
"all scales near the critical threshold $R_0=1$. In this exercise, you will\n",
"briefly consider three types of epidemic models (compartmental\n",
"models, network models, and lattice models), compare different\n",
"social interventions designed to lower $R_0$, and explore the fluctuations\n",
"and critical behavior very close to threshold.\n",
"\n",
"Compartmental models use coupled differential equations to model\n",
"the disease spread between different 'compartments' of the population.\n",
"The classic SIR model (see Exercise~6.25) involves three\n",
"coupled compartments,\n",
"\\begin{equation}\n",
" \\frac{d{S}}{d{t}} = -\\beta I S,\n",
" \\quad \\frac{d{I}}{d{t}} = \\beta I S - \\gamma I,\n",
" \\quad \\frac{d{R}}{d{t}} = \\gamma I,\n",
"\\end{equation}\n",
"where $S(t)$, $I(t)$, and $R(t)$ are the proportions of the population\n",
"that are susceptible, infected, and recovered. The parameter $\\beta$\n",
"measures the rate of infection spreading contact between people and\n",
"$\\gamma$ is the rate at which people recover.\n",
"\n",
"Network models treat people as nodes, connected to their contacts with edges.\n",
"They assume a *transmissibility* $T$, the average probability that a\n",
"victim will infect each of their contacts. For low $T$ the epidemics\n",
"die out; there will be a critical $T_c$ above which a large outbreak\n",
"will continue to grow exponentially. There are a\n",
"variety of networks studied:\n",
"fully connected networks (where SIR models become valid), loopless branching\n",
"tree networks where everyone has $k$ neighbors, real-world networks\n",
"gleaned from data on households and school attendance, and\n",
"scale-free networks with a power-law distribution $p(k) \\propto k^{-\\alpha}$\n",
"for the probability that a person has $k$ contacts (has {\\em degree $k$}).\n",
"(Scale-free networks have been found to approximate the pattern of\n",
"interactions between proteins in cells and nodes on the Internet,\n",
"and serve as our model for populations with wide variation in the number\n",
"of social contacts with potential for disease transmission.)\n",
"\n",
"Lattice models -- networks in two dimensions where only near neighbors\n",
"are contacts -- are sometimes used in agricultural settings, where the\n",
"plant victims are immobile and the disease is spread only by direct proximity.\n",
"\n",
"(a) *\n",
"Write $R_0$ for the SIR model in terms of $\\beta$ and $\\gamma$,\n",
"for an initially nearly uninfected population ($S\\approx 1$ and $I \\ll 1$).\n",
"*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Network models usually ignore the long-range correlations between nodes:\n",
"except for real-world networks, the contacts are usually picked at random\n",
"so there are very few short loops. In that limit,\n",
"Meyers et al. express $R_0$ in terms of the\n",
"moments $\\langle k^n \\rangle = \\sum k^n p(k)$ of the degree distribution,\n",
"which they solve for using generating functions\n",
"(see Exercise 2.23):\n",
"\\begin{equation}\n",
"R_0 = T \\left(\\frac{\\langle k^2 \\rangle}{\\langle k \\rangle} - 1\\right).\n",
"\\end{equation}\n",
"\n",
"People like nurses and extroverts with a lot of contacts can act as\n",
"'super-spreaders', infecting large numbers of colleagues. Scale-free\n",
"networks explore what happens with a range of contacts: the smaller\n",
"the exponent $\\alpha$, the larger the range of variation.\n",
"\n",
"(b) *\n",
"What is the critical transmissibility $T_c$ predicted\n",
"by the network model in the above equation?\n",
"Show that, for a scale-free network with $\\alpha\\le3$\n",
"the critical transmissibility $T_c = 0$;\n",
"no matter how unlikely a contact will cause disease spread,\n",
"there are rare individuals with so many contacts that they (on average)\n",
"will cause exponential growth of the pathogen.\n",
"If our population had $\\alpha = 3$,\n",
"what percentage of the people would we need to vaccinate to immunize\n",
"everyone with more than 100 contacts? What would the resulting $T_c$,\n",
"the maximum safe transmissibility, be?\n",
"<\\em>\n",
"(If you find that the first percentage is small, use that fact\n",
"to simplify your calculation of $T_c$. Hint: $\\sum_1^\\infty k^{-z} = \\zeta(z)$, the Riemann zeta function,\n",
"which diverges at $z=1$.)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"An important limitation of these network results is that they assume the\n",
"population is structureless: apart from the degree distribution, the\n",
"network is completely random. This is not the case in a 2D square lattice,\n",
"for example. It has degree distribution $p_k = \\delta_{k, 4}$,\n",
"but connections between nodes are defined by the lattice, and not\n",
"randomly assigned. As you might expect, disease\n",
"spread is closely related to percolation. In the mean-field theory,\n",
"percolation predicts that the epidemic size distribution exponent is\n",
"$\\tau=3/2 = 1.5$; you will explore this in parts~(e) and~(f).\n",
"In 2D, the lattice structure changes the universality class,\n",
"the epidemic sizes are given by the cluster-size distribution exponent\n",
"$\\tau = 187/91 \\approx 2.055$.\n",
"\n",
"Besides exhibiting different\n",
"power-law scaling, the value of the critical transmissibility can be quite\n",
"different in structured populations.\n",
"\n",
"(c) **\n",
"What is $T_c$ for a tree with $k=4$ branches at each node\n",
"(so $p(k) = \\delta_{k,4}$)? Compare that to the critical transmissibility\n",
"for a 2D square lattice, $T_c = 0.5384$ (Tome et al.). Which is more\n",
"resistant to disease spread?\n",
"*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"One might imagine that a lattice model would mimic the effect of travel\n",
"restrictions to prevent disease spread. Travel restrictions reduce the\n",
"contact numbers, but do not change the qualitative behavior. This is\n",
"due to the 'small world phenomenon': a surprisingly\n",
"small number of long-range contacts can change the qualitative behavior\n",
"of a network (see Exercise 1.7).\n",
"Only a few long-distance travelers are needed to make our world well connected.\n",
"\n",
"Finally, let us numerically explore the fluctuations and scaling behavior\n",
"exhibited by epidemics at their critical points. We shall assume (correctly)\n",
"that our population is well connected. We shall also assume that our\n",
"population does not have system-scale heterogeneities: we ignore cities,\n",
"subpopulations of vulnerable and crowded people, and events like the Olympics.\n",
"Given these assumptions, one can argue that the qualitative behavior\n",
"near enough to the critical point $R_0=1$ is {\\em universal}, and controlled\n",
"not by the details of the network or SIR model but only by the distance\n",
"$1-R_0$ to the critical point.\n",
"\n",
"Let us organize our victims in 'generations' of infected people, with\n",
"$I_{n+1}$ the number of victims infected by the $I_n$ people in\n",
"generation $n$; we shall view the generation as roughly corresponding to\n",
"the time evolution of the pandemic. The mean $\\langle I_{n+1}\\rangle =\n",
"R_0\\,I_n$, but it will fluctuate about that value with a Poisson\n",
"distribution, so $I_{n+1}$ is a random integer chosen from a Poisson\n",
"distribution with mean $R_0$ $I_n$.\n",
"\n",
"(d)*\n",
"Write a routine pandemicInstance, that returns\n",
"the evolution vector\n",
"\\begin{equation}\n",
"[(0,I_0), (1,I_1) \\dots (n,I_n) \\dots],\n",
"\\end{equation}\n",
"and the total size $S = \\sum_n I_n$. Iterate your routine with\n",
"with $R_0 = 0.9999$ and $I_0 = 1$ in a loop until you find an epidemic with\n",
"size $S \\ge 10^5$. Plot the trajectory of this epidemic, $I_n$ vs. $n$.\n",
"Does your epidemic nearly halt during the time\n",
"interval? Do the pieces of the epidemic before and after this near halt\n",
"appear statistically similar to the entire epidemic?\n",
"*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def pandemicInstance(R0=1., i0 = 1):\n",
" I = i0\n",
" t = 0;\n",
" Itraj = [I]\n",
" size = i0\n",
" while I!=0:\n",
" I = random.poisson(R0*I)\n",
" size += ...\n",
" Itraj.append(...) \n",
" return size, Itraj\n",
"\n",
"size = 0;\n",
"while size < 1000000:\n",
" size, traj = pandemicInstance(...) \n",
"plot(traj)\n",
"title(\"size is \"+ str(size))\n",
"xlabel(\"Time in shells\")\n",
"ylabel(\"Infected in this shell\")\n",
"show()\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"One might presume that these large fluctuations could pose a real challenge\n",
"to guessing whether social policies designed to suppress a growing\n",
"pandemic are working. We must note, however, that the fluctuations\n",
"are important only near $R_0=1$, or when the infected population becomes small.\n",
"\n",
"At $R_0=1$, the size of the epidemic $S$ has a power-law probability\n",
"density $P(S) \\propto S^{-\\tau}$ for large avalanches $S$.\n",
"\n",
"(e) *\n",
"Write a routine pandemicEnsemble that does not store the\n",
"trajectory, but instead runs $N$ epidemics at a given value of $R_0$,\n",
"and returns a list of their sizes. Plot a histogram of the sizes of\n",
"$10^4$ epidemics with $R_0=0.99$, with, say, 100 bins.\n",
"*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def pandemicEnsemble(N, R0=1., i0 = 1):\n",
" sizes = []\n",
" durations = []\n",
" for n in range(N):\n",
" I = ...\n",
" size = i0\n",
" while I!=0:\n",
" I = random.poisson(R0*I)\n",
" size += ...\n",
" sizes += [...]\n",
" return sizes\n",
"\n",
"sizes = pandemicEnsemble(10**4, R0=...)\n",
"hist(sizes, bins=100)\n",
"title(\"Epidemic size histogram\")\n",
"xlabel(\"Size\")\n",
"ylabel(\"Counts\")\n",
"show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Regular histograms here are not useful; our distribution has a long\n",
"but important tail of large events. Most epidemics subside quickly\n",
"at this value of\n",
"$R_0$, but a few last for hundreds of generations and infect tens of\n",
"thousands of people. We need to convert to logarithmic binning.\n",
"\n",
"\n",
"(f) *\n",
"Change the bins used in your histogram to increase logarithmically, and\n",
"be sure to normalize so that the counts are divided by the bin `width'\n",
"(the number of integers in that bin) and the number of epidemics being\n",
"counted. Present the distribution of sizes for $10^4$\n",
"epidemics at $R_0=0.99$ on log-log plots. On the same plot, show the\n",
"power-law prediction $\\tau=3/2$ at the critical point.\n",
"*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def intlogspace(start, stop, num=50, endpoint=True, base=10.0):\n",
" realBins = logspace(start, stop, num, endpoint, base)\n",
" bins = unique(realBins.astype(int))\n",
" return bins"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"sizes = pandemicEnsemble(10000, R0=0.99)\n",
"\n",
"def logbinnedHist(vals, nBins=30, exponent=3/2):\n",
" maxval = max(vals)\n",
" bins = intlogspace(0,int(log10(maxval))+1,nBins)\n",
" widths = (bins[1:]-bins[:-1])\n",
" counts, edges = histogram(vals, bins=bins)\n",
" hist_norm = counts/(widths*len(vals))\n",
" plot(bins, (exponent-1) * bins**(-exponent), color='green', linewidth=3, label=\"Power law -\"+str(exponent))\n",
" bar(bins[:-1], hist_norm, widths, bottom=10**(-9)) # bottom avoids problems with log(0)\n",
" xscale('log')\n",
" yscale('log') # or 'log=True' in bar)\n",
" legend()\n",
"\n",
"logbinnedHist(sizes)\n",
"title(\"Epidemic size probability distribution\")\n",
"xlabel(\"Size S\")\n",
"ylabel(\"P(S)\")\n",
"show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In Exercise 12.28 we derived the universal scaling form for the avalanche size distribution in the random-field Ising model. This calculation also applies to our pandemic model. It predicts that the probability $P(S)$ of an epidemic of size $S$ for small distance $r=(1-R0)$ below the critical point is given by \n",
"$$P(S) = C S^{-3/2} e^{-S r^2/2},$$ where the nonuniversal constant $C$ is around $0.4$ to $0.5$ (depending\n",
"on the small $S$ cutoff).\n",
"Note that this is the predicted power law $\\tau=3/2$, but cut off above a typical size that grows quadratically in $1/r$.\n",
"\n",
"(g) Multiply your data and the scaling prediction by $S^{3/2}$ to make them near constant for small sizes (to make it easier to study the cutoff). Plot both on a log-log plot. Does the universal scaling function describe your simulated epidemic ensemble?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def logbinnedHistScaling(vals,R0=0.9,C=0.45):\n",
" num = len(vals)\n",
" maxval = max(vals)\n",
" bins = intlogspace(0,int(log10(maxval))+1,30)\n",
" widths = (bins[1:]-bins[:-1])\n",
" centers = (bins[1:]+bins[:-1])/2\n",
" counts, edges = histogram(vals, bins=bins)\n",
" counts_rescaled = ...*counts/(num*widths)\n",
" plot(centers, C*exp(-centers*(1-R0)**2/2), color='green', linewidth=3, label=\"Universal prediction\")\n",
" plot(centers, ..., label = \"Rescaled data\")\n",
" minY = max(min(counts_rescaled),10**(-4))\n",
" ylim(minY,10**0)\n",
" title(\"Epidemic size distribution scaling plot\")\n",
" xlabel(\"Size S\")\n",
" ylabel(r\"S**(3/2) P(S)\")\n",
" xscale('log')\n",
" yscale('log')\n",
" \n",
"sizes = pandemicEnsemble(10000, R0=0.9)\n",
"logbinnedHistScaling(sizes,R0=0.9)\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"The tools we learn in statistical mechanics -- generating functions,\n",
"universality, power laws, and scaling functions -- make tangible\n",
"predictions for practical models of disease propagation. They\n",
"work best in the region of greatest societal importance $R_0\\approx 1$,\n",
"where costly efforts to contain the pandemic are minimized while avoiding\n",
"uncontrolled growth."
]
}
],
"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.5.6"
}
},
"nbformat": 4,
"nbformat_minor": 0
}