CS 152: Project 4

Title image Project 4
Fall 2019

Project 4: Population Variability Analysis

For project 4 we're going to move to the world of ecological modeling. In particular, we're going to model the change in the population of Galapagos Penguins over time given certain ecological pressures. Our goal is to model the risk of extinction over some number of years. The model we will use is fairly simple, but you can choose to add complexity to the model once the basic system is working.

One new aspect for this project is the use of random values in the simulation. Because the world doesn't exhibit strong regularity, we're going to model this uncertainty by using a random number generator. For example, El Nino, an important factor in Galapagos Penguin survival, does not occur on a perfectly regular schedule, but it does tend to occur at least once every 5-7 years. We can model this by assigning a 1.0/5.0 or 1.0/7.0 probability of an El Nino to any given year. Then, each year of the simulation, we effectively roll a 5-sided or a 7-sided die and declare an El Nino year if it comes up with a 1. If the year is an El Nino year, then the penguin population drops significantly. If it is not an El Nino year, the population sees a small amount of growth.

Because of the random variables in the model, each time we model 100 years the computer will generate a different result. In one simulation, the population may go extinct in 20 years, in another simulation it may not go extinct at all. In order to achieve a reasonable estimate of the true probability of extinction over 100 years, we have to run the simulation many, many times and aggregate the results. For example, the probability of extinction in 100 years is the number of simulations where the population goes extinct, divided by the total number of simulations. Researchers using PVA to evaluate the risk of extinction in actual populations might run the simulation 10,000 times or more in order to get a stable estimate of extinction risk. Stability, in this case, means that if you run the simulation 10,000 times, then the difference between that average and the average of a different run of 10,000 simulations will be small.

To design the simulation, we're going to use hierarchical, modular design in order to keep each function simple to write and debug. Think about the project as having layers. The top layer is the controlling function and contains three parts. The first part sets up the parameters for the simulation. The second part runs the complete simulation many times and stores the results. The third part analyzes the results and generates the desired outputs.

The next layer down handles running a single simulation over N years. The full simulation function has two main parts. First, it sets up the initial population, then it simulates N years using a loop. It returns either the year in which the population went extinct or N if the population is still viable at the end of the simulation.

The bottom layer handles simulating a single year of the simulation. It takes in the current population and the ecological parameters and determines how many individuals are in the next year's population. It then returns the new population.

By designing the simulation in this manner, each function is well-defined and simple to write and debug. The tasks below provide more details on each level and the overall task.


Tasks

  1. Setup

    If you haven't already set yourself up for working on the project, then do so now.

    1. Mount your directory on the Personal server.
    2. Open the Terminal and navigate to your project4 directory on the Personal server.
    3. Open TextWrangler. If you want to look at any of the files you have already created, then open those files.

    Create a new file penguin.py. For this week, all of the simulation functions will be in this file, though you can use the functions in your stats.py file to analyze the results, if you choose to pursue one of those extensions.

  2. Write a function to initialize the population

    Create a function initPopulation, which takes in two parameters: the initial population size and the probability of an individual being female. The function should return a list of the specified size. Each entry in the list will be either an 'f' or an 'm'.

    + (more detail)

    1. Define a function initPopulation with two parameters (e.g. N, and probFemale)
    2. Initialize a variable (e.g. population) to the empty list.
    3. Start a loop for the size of the population. Each time through the loop generate a random number using random.random(). If the value is less than the probability of an individual being female (probFemale), append an 'f' to the population list. Otherwise, append an 'm' to the population list.
    4. Finally, return the population list.

    Use the following test function to see if your initPopulation function is doing the right thing. It should print out a list of 10 individuals with an appropriate mix of 'f' and 'm' elements. Run it a few times to make sure you're getting randomized lists. (Remember to entab after copying code from the web page.)

    # test function for initPopulations
    def test():
        popsize = 10
        probFemale = 0.5
    
        pop = initPopulation(popsize, probFemale)
    
        print( pop )
    
        return
    
    if __name__ == "__main__":
        test()
  3. Write a function to simulate a single year

    Create a function simulateYear with six parameters:

    • pop: the population list
    • elNinoProb: the probability of an El Nino
    • stdRho: the growth factor in a regular year. This number is meant to allow the population to grow each year and is expected to be greater than 1.
    • elNinoRho: the growth factor in an El Nino year. This number is meant to reduce the population and is therefore less than 1.
    • probFemale: the probability of a new individual being female,
    • maxCapacity: the max carrying capacity of the ecosystem.

    The first step in the function is to determine if it is an El Nino year. Set a variable (e.g. elNinoYear) to False. Then compare the result of random.random() to the El Nino probability. If the random.random() result is less than the El Nino probability, then set elNinoYear to True.

    The second part of the function is a loop over the existing population list. Before starting the loop, create a list newpop and set it to the empty list. Inside the loop, implement the following algorithm.

        # for each penguin in the original population list
            # if the length of the new population list is greater than maxCapacity
                # break, since we don't want to make any more penguins
    
            # if it is an El Nino year
                # if random.random() is less than the El Nino growth/reduction factor
                    # append the penguin to the new population list
            # else
                # append the penguin to the new population list
                # if random.random() is less than the standard growth factor - 1.0
                    # if random.random() is less than the probability of a female
                        # append an 'f' to the new population list
                    # else
                        # append an 'm to the new population list
    						

    Finally, return the new population list.

  4. Test the simulateYear function

    To test your simulateYear function, add the following code to your test function.

        newpop = simulateYear(pop, 1.0, 1.188, 0.4, 0.5, 2000)
    
        print( "El Nino year" )
        print( newpop )
    
        newpop = simulateYear(pop, 0.0, 1.188, 0.4, 0.5, 2000)
    
        print( "Standard year" )
        print( newpop )
    						

    You should see the population reduce to 3-6 individuals in the El Nino year and grow to 11-14 individuals in the standard year. Run it a few times to see the variation.

  5. Write a function to run a single simulation

    The next step is to write the runSimulation function. This function takes in 8 parameters. You can use the definition below, if you wish, but feel free to modify the parameter names to ones that might be more clear to you.

    def runSimulation( N,            # number of years to run the simulation
                       initPopSize,  # initial population size
                       probFemale,   # probability a penguin is female
                       elNinoProb,   # probability an El Nino will occur in a given year
                       stdRho,       # population growth in a non-El Nino year
                       elNinoRho,    # population growth in an El Nino year
                       maxCapacity,  # maximum carrying capacity of the ecosystem 
                       minViable ):  # minimum viable population
    						

    The function should initialize a population, then loop N times. Inside the loop it should call simulateYear and assign the return value back to the population list. If, after simulating a year, the population is smaller than the minimum viable population or it consists of only one gender, then the function should return the year of extinction. If the loop completes all N times and there is still a viable population, the function should return N.

    + (more detail)

    The function should first assign to a variable (e.g. population) the result of calling initPopulation with the appropriate arguments. Then it should set another variable (e.g. endDate) to N (the number of years to run the simulation). The variable endDate will be what the function returns.

    The main part of the function should be a loop that runs N times. Each time through the loop it should:

    1. call simulateYear with the appropriate arguments, assigning the result to a new list variable (e.g. newPopulation),
    2. test if there is a viable population, and
    3. assign to population the new population

    A viable population must have at minViable individuals and there has to be at least one male and one female. If any of these tests fails, then set endDate to the loop variable value and break out of the loop. The Python keyword break will cause execution to stop looping and start executing the code after the loop.

    If you want to graph or view the population numbers over time, print out the length of the population list at the end of each loop. Alternatively, you could save the numbers to a file.

  6. Test runSimulation

    Add some test code to your test function that calls runSimulation a few times with some appropriate arguments. Make sure the initPopSize argument is larger than the minViable argument. Using a small value for the probability of an El Nino (e.g. 0.1) should result a return value of N (the population should survive). I fyou use a large probability of El Nino (e.g. 0.5) the return value should be much less than N.

    Default values for the simulation arguments are as follows:

    N201
    Initial Population Size500
    Probability of Females0.5
    Probability of an El Nino1.0/7.0
    Growth Factor in a standard year1.188
    Growth Factor in an El Nino year0.41
    Maximum Carrying Capacity2000
    Minimum Viable Population10

  7. Define a main function that runs many simulations

    The top level function is your main function. Give it one argument, argv, which will be the list of strings from the command line.

    1. Usage statement

      Start the main function by testing if there are at least three arguments on the command line. The first argument will be the name of the program, the second should be the number of simulations to run, and the third should be the typical number of years between an El Nino event. If there are less than three (len(argv) < 3), print out a usage statement and exit.

    2. Extract values from the command line arguments

      After the test for arguments, cast the second argument (argv[1]) to an integer and assign it to a variable that specifies the number of simulations to run (e.g. numSim). Cast the third argument (argv[2]) to an integer and assign it to a variable that specifies the typical number of years between an El Nino event.

    3. Set up local variables

      Create variables for each of the simulation parameters in the table above not already assigned and give them the default values. You should also create a variable to hold the results of the simulations and initialize it to the empty list.

    4. Write the main loop that runs simuations

      Loop for the number of simulations. Inside the loop, append to your result list the result of calling runSimulation with the appropriate arguments. This list will hold the year of extinction for each simulation run.

    5. Calculate the probability of survival after N years

      Count how many of the results in the result list are less than N (the number of years to simulate). Divide that count by the total number of simulations and print out the result. This is the overall probability that the penguins will go extinct within the next N years.

    Run your program using 100 simulations and see what you get. Does it change a lot if you run it repeatedly? Run it with 1000 simulations and see if the results are more stable.

  8. Compute the Cumulative Extinction Probability Distribution

    The next task is to write a function that takes the list of results, which is a set of numbers indicating the last year in which the population was viable, and convert it to a cumulative extinction probability distribution (CEPD). The CEPD will be a list that is as long as the number of years in the simulation. Each entry Y in the CEPD is the number of simulations where the population has gone extinct by year Y divided by the total number of simulations.

    The computeCEPD function should take two arguments: the list of results from runSimulation (a list of integers), and the number of years the simulation ran (N).

    The computeCEPD function has three parts.

    1. The first part is to create a list (e.g. CEPD) with N zeros. You can do this by appending N zeros to an empty list in a loop.
    2. The second part is to loop over the list of results (extinction years). If the extinction year is less than N, loop from the extinction year to N and add one to each of those entries in the CEPD list.
    3. The third part is to loop over the CEPD list and divide each entry by the length of the extinction year results list, which is also the number of simulations. After this, return the CEPD list.

    Add a call to the computeCEPD function to the end of your top level function. Then have your top level function print out every 10th entry in the CEPD list (remember how the range function works). Run your penguin simulation 1000 times using the standard parameters. Repeat the process three times and show the results in your writeup. How much variation is there in the results?

    Required plot set one: three plots of the CEPD for three runs of 1000 simulations for 201 years with the default parameters.

  9. Compare 3, 5, and 7-year El Nino cycles

    The final step is to use your simulation to compare the extinction distribution curves for a 3-year El Nino cycle, a 5-year El Nino cycle, and a 7-year cycle. What do your results indicate?

    Required plot set two: a plot of the CEPD for the three El Nino cycle options.


Follow-up Questions

  1. What is the difference between the following two code snippets?
    # example 1
    a = [5, 10, 15, 20]
    for i in range(len(a)):
        print(a[i])
    
    # example 2
    a = [5, 10, 15, 20]
    for x in a
        print(x)
  2. Why test code incrementally? Why not write all of the code and test it once?
  3. Why do we use random numbers in this population model?
  4. What is your favorite wild (undomesticated) animal?

Extensions

Extensions are your opportunity to customize your project, learn something else of interest to you, and improve your grade. The following are some suggested extensions, but you are free to choose your own. Be sure to describe any extensions you complete in your report.


Submit your code

Turn in your code (all files ending with .py) by putting it in a directory in the Courses server. On the Courses server, you should have access to a directory called CS152, and within that, a directory with your user name. Within this directory is a directory named Private. Files that you put into that private directory you can edit, read, and write, and the professor can edit, read, and write, but no one else. To hand in your code and other materials, create a new directory, such as project1, and then copy your code into the project directory for that week. Please submit only code that you want to be graded.

When submitting your code, double check the following.

  1. Is your name at the top of each code file?
  2. Does every function have a comment or docstring specifying what it does?
  3. Is your handin project directory inside your Private folder on Courses?


Write your project report

For CS 152 please use Google Docs to write your report. Create a new doc for each project. Start the doc with a title and your name. Attach the doc to your project on Google classroom. Make sure you click submit when you are done. The graders cannot provide feedback unless you click submit.

Your intended audience for your report is your peers not in the class. From week to week you can assume your audience has read your prior reports. Your goal should be to be able to use it to explain to friends what you accomplished in this project and to give them a sense of how you did it.

Your project report should contain the following elements.


Thanks to Cathy Collins for the project idea and documentation. The original project concept and idea came from "Problem-Solving in Conservation Biology and Wildlife Management, 2nd Edition", by James P. Gibbs, Malcolm L. Hunter, Jr., Eleanor J. Sterling.