15-110 Principles of Computation

Flu Virus Simulation - Example

This web page outlines the rules for the simulation and some sample code written and discussed in class.

Overview

In this example, we will develop a simple graphical simulation that shows how disease spreads through a population. Each time step of the simulation represents one day. A person starts off as healthy. Each day, a healthy person comes in contact with four random people. If any of those random people is contagious, then the healthy person becomes infected. It takes one day for the infected person to become contagious. After a person has been contagious for 4 days, then the person is non-contagious and cannot spread the virus nor can the person get the virus again due to immunity.

This simulation will show how the virus spreads in a population of 400 people, represented by a square 20 × 20 matrix. On the first day of the simulation, one person becomes infected with the virus.

Here is a sample of what the first eight "days" of the simulation might look like:


Each snapshot above is a time step of the simulation shown in a 200 × 200 window (in pixels). Notice that the window is "divided" up into 10 × 10 squares (in pixels). Each of these 400 squares represents one person in the simulated population.

To perform the simulation, you will need to use the Canvas object in RubyLabs. When you initialize a Canvas, you need to supply the size of the window and a title for the window. For example:

Canvas.init(200, 200, "Virus_Simulation")

creates a window of size 200 × 200 (in pixels) with a title of "Virus_Simulation". The origin of this window is at the top left corner (0,0), with the x coordinate increasing as you go from left to right, and the y coordinate increasing as you go from top to bottom.

To draw a square of size 10 × 10 with a fill color of green and an outline (border) of blue and its top left corner at coordinate (100,100) in the Canvas, we can execute:

Canvas::Rectangle.new(100, 100, 110, 110, :fill=>"green", :outline=>"blue")

The first two parameters are the top left coordinates (x,y) of the rectangle and the next two parameters are the bottom right coordinates (x,y) of the rectangle. The final two parameters specify the fill color and the outline (border) color as strings.

We will complete the steps below to build this simulation in Ruby.

  1. Write a function display(matrix) (in the file display.rb). The parameter is a 20 X 20 matrix representing the population for the simuation. Each person is a cell of the matrix. Each cell has an integer in the range 0 through 6 (inclusive), which encodes the person's health as follows:

    0    healthy, not infected
    1    infected
    2    contagious (day 1)
    3    contagious (day 2)
    4    contagious (day 3)
    5    contagious (day 4)
    6    non-contagious/immune
    

    Your function should go through the entire matrix and display each "person" as a square of size 10 pixels X 10 pixels in one of the following colors:

    white    healthy, not infected
    pink     infected
    red      contagious
    purple   non-contagious/immune
    

    General algorithm:

    1. For each row and column of the matrix, do the following:
      1. Set color equal to "white" if matrix[row][column] represents a healthy individual.
      2. Set color equal to "pink" if matrix[row][column] represents an infected individual.
      3. Set color equal to "red" if matrix[row][column] represents a contagious individual.
      4. Set color equal to "purple" if matrix[row][column] represents a non-contagious/immune individual.
      5. Draw a 10 X 10 square on the canvas based on the current row and column with a fill color as specified above and an outline of "black".

    Test your function using this Ruby function that creates a matrix of size 20 × 20 and fills each cell with a random integer between 0 and 6:

    def test_display()
            # create a canvas of size 200 X 200
            Canvas.init(200, 200, "Testing_Display")
            # initialize matrix a randomly
            a = Array.new(20)
            for i in 0..19 do
                    a[i] = Array.new(20)
                    for j in 0..19 do
                            a[i][j] = rand(7)
                    end
            end
    	# display the matrix using your display function
    	display(a)
    end
    

    Sample Usage:

    >> load "display.rb"
    => true
    >> load "test_display.rb"
    => true
    >> test_display()
    

    Your image will differ since we're using a random number generator.

    Sample solution:

    def display(matrix)
        for row in 0..matrix.length-1 do
            for col in 0..matrix[row].length-1 do
                person = matrix[row][col]
                if person == 0 then
                    color = "white"
                end
                if person == 1 then
                    color = "pink"
                end
                if person >= 2 and person <= 5 then
                    color = "red"
                end
                if person == 6 then
                    color = "purple"
                end
                Canvas::Rectangle.new(col*10, row*10, col*10+10, row*10+10,
                            :fill => color, :outline => "black")
            end
        end
    end
    
  2. Write a function update(matrix) (in the file update.rb). This function takes a 20 × 20 matrix representing our population, as described above, for the current time step of the simulation, and returns a new 20 × 20 matrix representing our population during the next time step (i.e. after one day has passed).

    The basic idea here is that we start with the current population in the array matrix and create a new snapshot of the population after one time step in an array new_matrix. Each cell new_matrix[row][column] represents the new status of the person from matrix[row][column].

    General Algorithm:

    1. Create a new 20 × 20 matrix, called new_matrix, with each cell initialized to 0. (HINT: Look at the test_display function above to see how to create and initialize a matrix.)
    2. For each row and column of the matrix, do the following:
      1. If matrix[row][column] represents a non-contagious/immune individual, set new_matrix[row][column] so this cell is also a non-contagious/immune individual.
      2. If matrix[row][column] represents an infected or a contagious individual, set new_matrix[row][column] so that it contains a value that is 1 + matrix[row][column]. (That is, the individual advances one position in the scale from 1-6.)
      3. If matrix[row][column] represents a healthy individual, do the following:
        1. Set new_matrix[row][column] equal to 0.
        2. Repeat the following four times:
          1. Pick a random person from the matrix. (See NOTE below.)
          2. If that person is contagious, then set new_matrix[row][column] equal to 1 (which means the healthy individual will be infected for the next day).
    3. Return the new_matrix as the final result.

    Test your update function using the following function:

    def test_update()
            # create a canvas of size 200 X 200
            Canvas.init(200, 200, "Testing_Update")
            # initialize matrix a to all healthy individuals
            a = Array.new(20)
            for i in 0..19 do
                    a[i] = Array.new(20)
                    for j in 0..19 do
                            a[i][j] = 0
                    end
            end
    	# infect one random person
    	a[rand(20)][rand(20)] = 1
    	display(a)
    	sleep(2)
    	# run the simulation for 10 "days"
    	for day in 1..10 do
    		a = update(a)
    		display(a)
    		sleep(2)
    	end
    end
    

    Sample usage:

    >> load "display.rb"
    => true
    >> load "update.rb"
    => true
    >> load "test_update.rb"
    => true
    >> test_update()
    

    NOTE: For this problem, if an individual is healthy and you pick 4 random people to come in contact with, it is ok if you pick the same person twice or if you pick the current individual you are testing.

    Sample solution:

    def immune?(matrix, i, j)
        if matrix[i][j] == 6 then
           return true
        else
           return false
        end
    end
    
    def contagious?(matrix, i, j)
        if matrix[i][j] >= 2 and matrix[i][j] <= 5 then
           return true
        else
           return false
        end
    end
    
    def infected?(matrix, i, j)
        if matrix[i][j] == 1 then
           return true
        else
           return false
        end
    end
    
    def healthy?(matrix, i, j)
        if matrix[i][j] == 0 then
           return true
        else
           return false
        end
    end
    
    def update(matrix)
        #create new matrix, initialized to all zeroes
        newmatrix = Array.new(20)
        for i in 0..19 do
            newmatrix[i] = Array.new(20)
            for j in 0..19 do
                newmatrix[i][j] = 0
            end
        end
    
        #create next day
        for i in 0..19 do
            for j in 0..19 do
                 if immune?(matrix, i, j) then
                     newmatrix[i][j] = 6 
                 end
                 if infected?(matrix, i, j) or contagious?(matrix, i, j) then
                     newmatrix[i][j] = matrix[i][j] + 1
                 end
                 if healthy?(matrix, i, j) then
                     for k in 1..4 do     # repeat 4 times
                         if contagious?(matrix, rand(20), rand(20)) then
                             newmatrix[i][j] = 1
                         end
                     end
                 end
             end
         end
         return newmatrix
    end