menu COMPSCI 2120/9642/DIGIHUM 2220 1.0 documentation

Assignment 2: Simulating an Epidemic/Outbreak

_images/contact_tracing.jpg

In the previous assignment, you were introduced to some light data analysis with pandas. Building off of that experience, in this assignment we will attempt to simulate an epidemic/outbreak. Our simulation will feature a population of individual people, each with a ‘Person #’, a positive_flag (which is True if that person is positive for the virus), and a list of other people in proximity to that person. This simulation will allow us to build a “playground” of sorts wherein we can tweak certain aspects (such as the probability of transmission or probability of recovery) to theorize what would (or could) happen.

Before you start…

Step 1) Download the template

Step 2) Try to skim through the code to see if any of it makes sense. Like in the previous assignment, much of the code will be uncomfortable for you (this is expected, so don’t feel like you’re alone on that).

Focus on the docstrings (comments right under each function) — these should tell you everything you need to know about how to use each function – without you having to understand all of the details.

The Lists You’ll Use

Each person in our simulation is represented by a list: ['Person #', Positive_Flag, List_of_Others_in_Proximity]

  • Person # — that person’s associated number

  • Positive_FlagTrue if that person tests positive for the virus

  • List_of_Others_in_Proximity — another list of all others in the population who are in proximity to this particular person

Our overall population is a list which contains all of our person lists. Here’s an example of what this could look like:

  • population_list = [['Person 0', False, [0, 2, 3]], ['Person 1', False, [1, 2]], ['Person 2', False, [0, 1, 2]], ['Person 3', False, [0, 3]]

This population list has persons 0-3. Person 0, for example, is negative for the virus and is in proximity to persons 0, 2, and 3. In our simulation, person 0 should therefore only be able to transmit the virus to persons 2 or 3.

Your first lines of code

Before you start writing functions, try this:

>>> my_population = create_population_list(15)
>>> draw_network(my_population)

create_population_list(15) creates a list of 15 person lists, each in the form of ['Person #', Positive_Flag, List_of_Others_in_Proximity]. draw_network(my_population) displays your population’s network (i.e. the nodes and which nodes they are connected to).

Your tasks for this assignment

For this assignment you will need to write the following functions:

  1. new_positive_case(population_list, person_number). This function takes your population list population_list as a parameter and an integer person_number corresponding to the person whom will now be positive. To help, you may want to write a print statement such as print("New case: Person ", person_number)

Test that your function works by creating a population, drawing the network, calling the new_positive_case function, and drawing the network again. You should see that node 0 is now red (instead of blue).

>>> my_population = create_population_list(15)
>>> draw_network(my_population)
>>> new_positive_case(my_population, 0)
>>> draw_network(my_population)

To create a new positive case, set the “positive_flag” to True… i.e. set population_list[person_number][1] to True, where [person_number] is the index of the person and [1] is the index of the positive_flag.

  1. transmit(population_list, person_number, p_transmission). This function takes your population list, the person # of the person who may transmit the virus, and the probability of transmission as arguments.

For each person in proximity to person_number, the function must (with probability p_transmission) transmit the virus.

Here is a hint of how you might code this (in pseudocode or “English-language” code:

if person is infected
  for each person i in their proximity list
    if(person i is negative) and np.random.rand() < p_transmission
      change positive_flag of person i to True
      print that Person (person_number) infects Person i
  1. recover(population_list, person_number). Here you will do the same thing as new_positive_case, except change the positive_flag to False (instead of True).

  2. simulate_step(population_list, p_transmission, p_recovery). This function will execute one step of your simulation (i.e. one round of transmission from each positive_case to all of those in proximity).

In each round, there are two aspects:

  1. A new person may become positive (random)

  2. A positive person may transmit to someone in their proximity list

Here is some pseudocode to help you out:

for new cases:

if (np.random.rand() < p_transmission)
  call new_positive_case function on a random person in your population_list

for transmission:

for each person in population list
  if person's positive_flag is True
    call the transmit function with probability p_transmission
  if person's positive_flag is True and np.random.rand() < p_recovery
    call the recovery function on that person
  1. all_cases_positive(population_list). This function returns True if all cases are positive and False otherwise.

Checkpoint

At this point, try running what you have to make sure that your functions are working:

>>> my_population = create_population_list(15)
>>> draw_network(my_population)
>>> new_positive_case(my_population, 0)
>>> draw_network(my_population)
>>> simulate_step(my_population, 0.8, 0.1)
>>> draw_network(my_population)
>>> simulate_step(my_population, 0.8, 0.1)
>>> draw_network(my_population)
>>> simulate_step(my_population, 0.8, 0.1)
>>> draw_network(my_population)

Use the code you just tested in your checkpoint to create your next function, simulate_run.

  1. simulate_run(population_list, p_transmission, p_recovery, first_positive_person) This function runs a simulation given a population list, probabilities for transmission and recovery, and an initial positive_case (integer for person number).

Like in your checkpoint code, you will need to do the following:

Call the new_positive_case function to create an initial case in your system.
  Draw your network
  While not all cases are positive
    Call the simulate_step function
    Draw your network
  1. simulate_run_no_draw(population_list, p_transmission, p_recovery, first_positive_person) Do the same thing that you did in your simulate_run function, but take out each line where you are drawing the network. Return the number of steps (i.e. the number of times you called the simulate_step function) it took in order for all cases to become positive.

  2. simulate_many(population_size, p_transmission, p_recovery, first_positive_person, num_runs) This is your final function, and it will allow you to run the simulation many times (num_runs times). Essentially you will call the simulate_run_no_draw function num_runs times, and you will return the average number of steps required for all cases to become positive (across all of your runs). To do this, you can create a list (num_steps_list) which, for each run, stores the number of steps taken until all cases are positive. You can then calculate the average value of the list, and return that average.

For your final submission…

Once you have created all of your functions, the only code you need outside of your functions is two function calls (1. simulate_run and 2. simulate_many) and a print statement with the average number of steps required for complete transmission. Make sure to save your final figure from simulate_run to include in your submission.

>>> simulate_run(create_population_list(15), 0.8, 0.1, 0)
>>> average_steps = simulate_many(15, 0.8, 0.1, 0, 10000)
>>> print("Avg. steps for complete transmission: ", average_steps)

Call your simulate_run function with the following values:

  • population_list = create_population_list(15)

  • p_transmission = 0.8

  • p_recovery = 0.1

  • first_positive_person = 0

Call your simulate_many function with the following values:

  • population_size = 15

  • p_transmission = 0.8

  • p_recovery = 0.1

  • first_positive_person = 0

  • num_runs = 10000

Make sure that you are commenting where you can (also, include a docstring for each function).

Important Update

Some tips for your final submission:

  • Print statements other than the one for average steps are not necessary in your final output, so please comment them. These are intended for your own understanding of the simulation and, ideally, should be taken out once you’re ready to submit.

  • If you find that it’s taking too long to click through each network drawing to get the final one, feel free to try one of the following:

  1. Only call draw_network outside of the while loop for simulate_run… this will help if you just want the final network diagram.

  2. Use a smaller population size (for your diagram only) — this will allow you to quickly watch your population become all positive (and save your “all_pos.png”). For my purposes I only want to see that you have the correct programming logic, so a population size of 10, for example, is fine for the diagram (i.e. no marks deducted). Use 15 for your simulate_many function, though.

Note that as these tips were not initially included on this page, of course I won’t penalize you for not following them. Though, it is recommended that you follow them to make things easier.

What to submit on OWL (in zipped folder)

  • your code (a2_for_studentsf.py) with comments (including docstrings for functions)

  • your final figure for one run of the simulation (call it “all_pos.png”)

  • your output (copy and paste to a .txt file, call it “output.txt”)