Homework 4: Population Simulation

The purpose of this assignment is to practice using if statements and data structures. It will also give you practice making small changes to existing code, rather than starting from scratch. Working with existing code is far more common than coding from scratch, so this is very practical experience. The existing code uses loops, lists, and dictionaries, which you’ve seen before, and functions, which you haven’t written yet.

Your program will simulate a population of animals (say, rabbits). The program will consider the animal’s health, age, and sex when deciding if the animal should live or die, and reproduce or not. The program keeps track of the number of animals each time unit (day, month, whatever) that are alive, have died, or have been born.

You will be given a large amount of skeleton code, with missing pieces to fill in. The code to fill in will only involve if statements, assignment statements, and manipulating lists and dicts.

Population data structure (animals)

The population is represented by a list of animals. Each animal is represented as a dictionary. A population might look like:

[{'age': 7, 'sex': 'F', 'health': 9.06, 'is_alive': True},
 {'age': 6, 'sex': 'M', 'health': 1.39, 'is_alive': True},
 {'age': 5, 'sex': 'F', 'health': 7.31, 'is_alive': True},
 {'age': 1, 'sex': 'F', 'health': 6.32, 'is_alive': True}]

This says that animal 0 is a Female, age 7, with health 9.06, and is alive.

If we’d wanted to access their data, we could write:

# get out the dictionary for just one animal
animal = animals[0]
print(animal) # prints the first line of "population looks like" above
print(animal['age'])
print(animal['sex'])
...

and we could change them with

animal = animals[0]
# make the animal a year older
animal['age'] = animal['age'] + 1
# a shorter way to do the same thing:
animal['age'] +=  1

A note about is_alive: When an animal dies, it isn’t immediately removed from the animals list. Instead, the is_alive flag (boolean value) for that animal is set to False instead of True. Dead animals are removed from the population (by vultures perhaps?) once per time step by code that is already provided.

Counters (counters) dictionary

The code needs to keep track of how many animals were born and died each time step. It keeps these counts in a dict named counters, which looks like:

{'num_died_of_old_age': 1, 'num_died_of_poor_health': 2, 'num_born': 5}

See display_population_stats for an example of how to get data out of the counters.

At the beginning of each step the counters are reset to 0. Every time an animal dies or is born, we increment the corresponding counter. For example (and forgive me the Oregon Trail reference):

counters['num_died_of_snakebite'] += 1

Step 1: Download the skeleton file

Begin by creating a new folder for this assignment called pop_sim.

Download this file. Put it in that folder and rename it to population.py.

Step 2: Tour the Code

Start by inspecting the code to answer these questions. You can write your responses in the documentation header of your code. They will not be graded.

  1. How many animals start out in the simulation?

  2. At what age does an animal die?

  3. What counts are kept for all animals for each time period?

  4. What does the function increment_age() do? How does it do it?

Find the code that follows the line # ---------------- main -----------------

  1. How does the first block of code create animals with (mostly) random properties?

  2. What does the “Main loop” code for each “day”. I.e., what function calls are made each day? Does the order matter?

  3. Go back to the top of the file and inspect the comment that describes how to use the DEBUG constant. Make sure you understand this code.

Note these important features of the code:

  1. Find the places where the word TODO exists in the code. These are where you will need to write your own code, or make changes to the provided code. You do not need to change any other code besides what’s marked with TODOs.

  2. The code makes extensive use of functions, which you have experience calling, but have not experienced writing yourself. You will have to make changes to these functions. Make sure your code is indented properly within the functions.

Step 3: Run the existing code

Execute the code and note what you see. You should see no animals being born or dying.

Make sure you understand the data structures:

Now you’re ready to go!

Step 4: Change update_health()

Replace the TODO comment in update_health() with real code. Note that you will need to use random.uniform(a, b) to get a random floating point number here.

Refer to increment_age as an example.

Since it’s tricky to see the effect of changes to individual functions, we’ve provided ZyBooks tests for individual functions. Check your solution by copying the entire update_health function (starting from def update_heath) into the corresponding ZyBooks test.

Step 5: Change update_alive()

Replace the TODO comment in update_alive() with real code.

Let me help you get started. You see this in the file:

# TODO: check if this animal should die due to 
# old age.  If so, 
# - print a debugging message that an animal died of old age
# - change is_alive to False for that animal
# - increment the correct counter
# otherwise, check if their health is 0 or less. If so:
# - print a debugging message that an animal died of poor health
# - change is_alive to False for that animal
# - increment the correct counter

Start by creating the if statement: leave the comments intact so they guide you. (Clean it up when you’re done.) The if statement is going to have to check the age of the animal against the DIE_AGE constant. I would write this code:

# Check if this animal should die due to old age.
if animal["age"] >= DIE_AGE:
    # print a debugging message that the animal i died of old age
    if DEBUG:
        print("a death from old age occurred")
    # change is_alive to False for that animal
    # increment the correct counter

Notice how I leave the comments (which describe the algorithm) in place, and add code below each comment line. I also removed the TODO.

Continue to add code in the rest of this function to implement the algorithm. Tips:

Step 5: Change can_mate()

Now we’re starting to work on the code for reproduction. The way this works is:

Your task is to replace the False in can_mate() with the correct condition. See the tips in the previous step.

The criteria are (also given in the skeleton code):

As usual, try your function in ZyBooks to check it out. Note that ZyBooks won’t catch every possible mistake.

Step 6: Change reproduce()

Replace the TODOs in reproduce(). Refer to the code just below it, commented Initialize first population of animals, for an example of how to make new animals. Try the result in ZyBooks.

Note: you can use random.uniform(0, 1) or random.random() to make a random number between 0 and 1.

Step 8: Observe the results

Run your code multiple times and observe the results. Does your code often make it through NUM_DAYS_TO_SIMULATE or do all animals die before that? Does your code ever get through all iterations without showing a population explosion?

Try tweaking some of the CONSTANTS at the top of the file to see if you can produce some runs where the population survives but there is not a population explosion.

Submit

Before you submit: