DEV Community

loading...
Cover image for Simple Disease Outbreak Modeling - Inspired by a 3b1b video

Simple Disease Outbreak Modeling - Inspired by a 3b1b video

QLabs
Musician, developer, and hobbyist. Write articles regularly on JS, Python, and other programming related things.
・6 min read

As the COVID-19 outbreak rages on, a good look at the outbreak would help. And when I saw a video by 3b1b on simulating epidemics, I tried to recreate what he did. So let's get started.

Since we need to put our graphics somewhere, we create a canvas in HTML and initialize it in JS.

<canvas id="c">
    Your browser does not support the canvas.
</canvas>
Enter fullscreen mode Exit fullscreen mode

And our JS:

var cv = document.getElementById("canvas")   
var c = cv.getContext("2d")
cv.width = window.innerWidth   
cv.height = window.innerHeight
Enter fullscreen mode Exit fullscreen mode

First, we need variables such as the population and infection rate
(quick note, we’ll be coding this in JS, so make sure you understand basic JS before taking this tutorial).

var population = 100   
var infected = 1 
var speed = 10   
var currentInfections = 1
Enter fullscreen mode Exit fullscreen mode

Okay, so it’s pretty self-explanatory, but let's just go over it. The population variable is the amount of dots/people in the simulation. The infected variable is the start number of infections, and we have this because there can be 10 or 20 ‘patient zeros’ in an outbreak. The speed is how fast the dots move, and currentInfections is the number of infections. Now, the reason we have infected and currentInfections is because currentInfections is how many we have at any given time during the outbreak, while infected is how many we have to start with.

Next, we need an array where we will store each value, such as whether it is infected, susceptible, or recovered; the dots x and y; and its velocity, which I will explain in a moment.

Before I get to velocity, I want to explain what our model is. Our model is called a SIR model, which stands for susceptible, infected, and recovered. The susceptible population can get infected, the infected population can infect others, and the recovered population can no longer infect others, and, in this model, no longer can be re-infected.

Now, let’s get to velocity. This is the direction of the dots, such as left, right, up, or down. We will have two parts, velocity x, and velocity y. This way, dots don’t only go up, down, left, and right, but also in diagonals.

Because writing out all of this will take too long, we will use a for loop. We first define our array:

var dots = []
Enter fullscreen mode Exit fullscreen mode

And now we will add to it:

for(var i = 0; i<population-infected;i++){
    dots.push([Math.random()*Math.min(cv.width,cv.height)*3/4,Math.random()*Math.min(cv.width, cv.height) * 3/4,0,speed *Math.random(),speed * Math.random()]
}
Enter fullscreen mode Exit fullscreen mode

Let’s go over it. Since each dot has several parts to it, we create sub-arrays inside it. The first 2 parameters are x and y. We place them in a random position on our canvas, but to place them in the center of the screen, we multiply them by 3/4.

Next, we have its state: susceptible, infected, or recovered. We can add more states like dead or immune instead of recovered, but let’s keep it simple for now. We put 0 as susceptible, 1 as infected, and 2 as recovered.

We have our velocity values next. We multiply the speed with a random number to get our velocity.

Now, you might have noticed that the for loop only covers 99 people, not 100. This is because we need to have a new for loop for the infected population.

for(var i = 0; i<infected;i++){
    dots.push([Math.random()*Math.min(cv.width,cv.height)*3/4,Math.random()*Math.min(cv.width,cv.height)*3/4,1,speed*Math.random(),speed*Math.random()]
}
Enter fullscreen mode Exit fullscreen mode

Now, we create a function called refresh() to do our animation.

var refresh = function () {
}
Enter fullscreen mode Exit fullscreen mode

Before we get to the animation and drawing, make sure to draw the boundary of the “city”, which is a white rectangle.

ctx.fillStyle = "rgb(255,255,255)"
ctx.strokeRect(cv.width*1/4,cv.height*1/4,cv.width*3/4,cv.width*3/4)
Enter fullscreen mode Exit fullscreen mode

Inside the function, we need to do our animation and drawing. We first draw circles for all the susceptible population, which will be blue, then the infected, which is red, and the removed/recovered population, which is grey. I’ll let you figure this one out.

Now, let’s animate them. We run a for loop which will go through the dots array and animates them.

for(var i = 0; i < population;i++){
  dots[i][3]+=Math.random()*2-1
  dots[i][4]+=Math.random()*2-1      
  if ( dots[i][3] >= speed ){dots[i][3] = 0}     
  if ( dots[i][3] <= -speed){dots[i][3] = 0}     
  if ( dots[i][4] >= speed ){dots[i][4] = 0}       
  if ( dots[i][4] <= -speed ){dots[i][4] = 0}      
  dots[i][0]+=dots[i][3]     
  dots[i][1]+=dots[i][4]      
  if(dots[i][0]>1*Math.min(cv.width,cv.height)*3/4){      
    dots[i][0]=1*Math.min(cv.width,cv.height)*3/4      
  }     
  if(dots[i][0]<0){
    dots[i][0]=0
  }
  if(dots[i][1]>1*Math.min(cv.width,cv.height)*3/4){
    dots[i][1]=1*Math.min(cv.width,cv.height)*3/4      
  }     
  if(dots[i][1]<0){
    dots[i][1]=0      
  }    
}
Enter fullscreen mode Exit fullscreen mode

Now that we have that done, we need to start infecting others. To do this, we run a nested for loop which will find the infected dots. Once we find them, we will run a nested for loop to find other dots within the infection radius, which we put as 5. I’ll let you figure this one out too since it shouldn’t be too hard (HINT: there’s a double nested for loop).

Now, we’ve infected, we’ve drawn, and we’ve animated. We just need one more thing. Since people either die or recover, we should add that in. We add another element to the sub-arrays inside dots. At the beginning of the for loop (the first one) we put this:

dots[i][5]++
Enter fullscreen mode Exit fullscreen mode

If you put a different variable in the for loop, replace i with that variable. At the end, put this:

if(dots[i][5] >= 200){dots[i][2] = 2}
Enter fullscreen mode Exit fullscreen mode

This adds a “timer” to the infected ones and once it reaches 200, it changes to one of the removed dots.

We’ve now accomplished everything! To draw and completely animate, put this at the end of refresh():

window.requestAnimationFrame(refresh)
Enter fullscreen mode Exit fullscreen mode

And then run the function:

refresh()
Enter fullscreen mode Exit fullscreen mode

NOTE: This model does not represent the COVID-19 outbreak or any other outbreak, but it can be a very, very, very, very, very simple way to represent an outbreak.

What to Add

Several things you can add include:

  • Dead and Recovered dots, instead of just the removed population as a whole
  • Social distancing (this can be achieved by not moving some of the dots, and the idea was from a post by Harry Stevens)
  • Quarantining a portion of the sick dots (only a portion, because not everyone who is sick shows symptoms and gets tested)
  • Adding several communities where people travel from one to the other (idea from the 3b1b video I mentioned)
  • Graphs, charts, data, etc.
  • More customization in the parameters such as the infection rate, speed, recovery rate, etc
  • A central spot, like a shop (also from the 3b1b video) For more complex modeling, I suggest you check out q9i’s article on disease modeling — Reopening Safely: The Data Science Approach on Medium (link at the end)

Examples

Here are some good examples

Further Reading

Discussion (3)

Collapse
crimsonmed profile image
Médéric Burlet • Edited

Interesting tutorial But I would have loved for you to modernize it using current best practices.

for instance:

for(let i = 0; i<infected;i++){
    dots.push([Math.random()*Math.min(cv.width,cv.height)*3/4,Math.random()*Math.min(cv.width,cv.height)*3/4,1,speed*Math.random(),speed*Math.random()]
}

Enter fullscreen mode Exit fullscreen mode

I would have also decomposed the above formula with step by step.

Another example is your refresh function declaration:

const refresh = () => {

}
Enter fullscreen mode Exit fullscreen mode

You might also want to look into p5 which has amazing drawing capabilities for this kind of work.

p5js.org/examples/simulate-flockin...

Collapse
quantalabs profile image
QLabs Author • Edited

Thanks for your feedback! Although p5 might have better drawing techniques and styles, I felt like the whole process of learning a new library and using it in the simulation defeats my purpose of keeping the simulation simple. I don't usually use const but it is something you could use as a substitute. These suggestions though should help the reader, and I encourage you to use what you thought was better or current. Once again, thanks for your feedback.

Collapse
crimsonmed profile image
Médéric Burlet

Const and let is not just to substitute. Its for scope, security and more