CS 152: Project 5

Title image Project 5
Fall 2019

Project 5: Simulating Elephant Population Management

As noted in the lab, this is the first of a three-part project where we'll be simulating the elephant population in Kruger National Part, South Africa. The carrying capacity of the park is approximately 7000 elephants (1 elephant per square mile of park). Previous efforts to manage the population involved culling approximately 400 animals per year. After the development of an elephant contraceptive, the current effort to manage the population involves using a contraceptive dart on adult female elephants to limit the birth rate.

The conceptual goal of this week's simulation is twofold. First, identify differences in the population distributions beween the two methods of limiting the population. Second, identify the percentage of adult females who have to be darted with the contraceptive each year in order to maintain the population without culling.

You already started the code for the project in lab. Following the overall design below, continue to develop the simulation and then use it to analyze the effect of different rates of contraceptive darting on the population.

control flow diagram


Tasks

  1. Write a function to calculate which elephants survive a year

    In the lab, you created the initPopulation and incrementAge functions. The next function to create is calcSurvival. The calcSurvival function goes through the elephant population list and determines whether each individual elephant survives to the next year. The function takes the parameter list and population list as arguments. The function uses the parameters max age and the three survival probabilities (calf, adult, senior).

    The function should loop over the existing population list and add each elephant to a new population list if it survives, using the elephant's age to determine which probability to use to determine survival. It should return the new population list.

    + (more detail)

    Create an empty list, new_population.

    Loop over the existing population list. Use the age of the elephant to determine which survival probability applies, then use the appropriate survival probability to see if the elephant should be added to the new_population list. Do this by testing if a call to random.random() is less than the survival probability.

    After the loop, return the new_population list.

    Test your function by downloading test_calcSurvival.py, reading the code to make sure you understand it. Read the comments to make sure you know what output to expect, run it, examine the output, and make any necessary changes to your code.

  2. Write a function to randomly dart female elephants

    Write a function, dartElephants, that goes through the adult females and randomly selects individuals for darting based on the dart probability parameter. The function takes in the parameter list and population list as arguments. It returns the population list. The function makes use of the probability of darting, the juvenile age, and the maximum age.

    + (more detail)

    A helpful first step is to assign the three parameters--probability of darting, juvenlie age, and maximum age--into local variables.

    The function should loop over each elephant in the population list. Inside the loop, it should test if the elephant is a female and is older than the juvenile age and is less than the maximum age.

    If the elephant is an appropriate age, then determine if the elephant should be darted by testing if a call to random.random() is less than the probability of darting.

    If the animal should be darted, set its pregnancy field to 0 and set the months of contraceptive left field to 22.

    After the loop, return the population list.

    Test your dartElephants function to make sure some fraction of the adult females are being modified correctly. You may use test_dartElephants.py

  3. Write a function to cull elephants from the population

    Write a function, cullElephants, that checks of there are more elephants than the carrying capacity. If there are too many elephants, it should remove enough randomly chosen elephants from the population so there are as many elephants as the carrying capacity. The function takes in the parameter list and population list as arguments. It should return a tuple containing first the new population list and second the number of elephants culled. The function makes use of the carrying capacity of the population.

    + (more detail)

    A helpful first step is to assign to a local variable the carrying capacity entry in the parameters list.

    Determine the number of animals that need to be culled (that is the total number of animals in the population minus the carrying capacity).

    If there are too many elephants, then randomly shuffle the population list (use random.shuffle). Use the appropriate list-slice indexing to keep the first carryingCapacity animals.

    Return a tuple containing this new list and then the number of animals culled. A tuple is simply a comma separated set of things inside parentheses. A tuple is like a list but uses parentheses instead of square brackets. The return statement might look something like the following.

    return (newPopulation, numCulled)

    Test the cullElephants function to make sure the population list is being modified correctly. You may use test_cullElephants.py

  4. Write a function to manage how to control the population

    Write a function, controlPopulation, that will determine whether the population should be darted or culled and call the appropriate function. It then returns the new population list and the number culled (which will be zero, if the elephants were darted) as a tuple.

    + (more detail)

    The following is the structure of the cullElephants function

    def controlPopulation( parameters, population ):
        # if the parameter value for "percent darted" is zero:
            # call cullElephants, storing the return values in a two variables 
            #   ( e.g. (newpop, numCulled) = cullElephants( parameters, population ))
        # else
            # call dartElephants and store the result in a variable named newpop
            # set a variable named numCulled to zero
        #  return (newpop, numCulled)

    Test your controlPopulation function to make sure the list is being modified correctly. You may use test_controlPopulation.py

  5. Write a function to simulate a month

    The function simulateMonth moves the simulation forward by one month. It modifies only the adult females in the population, and it adds a new calf to the population if one should be born. The function takes in the parameter list and the population list. It returns the population list. The function uses the calving interval, juvenile age, and maximum age parameters.

    This is the function most prone to cause problems, so think carefully about your logic.

    + (more detail)

    It is helpful to assign the three parameters to local variables.

    Loop over the population list. The algorithm inside the loop is given below. After the loop is complete, return the population list.

    To determine whether a fertile elephant becomes pregnant, use the calving interval and the fact that a gestation is 22 months. When a female is not already pregnant and not on contraceptive, the chance per month of her getting pregnant is 1.0 / (3.1*12 - 22).

        for e in population:
            # assign to gender the IDXGender item in e
            # assign to age the IDXAge item in e
            # assign to monthsPregnant the IDXMonthsPregnant item in e
            # assign to monthsContraceptive the IDXMonthsContraceptiveRemaining item in e
    
            # if gender is female and the elephant is an adult
                # if monthsContraceptive is greater than zero
                    # decrement the months of contraceptive left (IDXMonthsContraceptiveRemaining element of e) by one
                # else if monthsPregnant is greater than zero
                    # if monthsPregnant is greater than or equal to 22
                        # create a new elephant of age 1 and append it to the population list
                        # reset the months pregnant (the IDXMonthsPregnant element of e) to zero
                    # else
                        # increment the months pregnant (IDXMonthsPregnant element of e) by 1
                # else
                    # if the elephant becomes pregnant
                        # set months pregnant (IDXMonthsPregnant element of e) to 1

    Make sure you understand the difference between a local variable like monthsPregnant and the IDXMonthsPregnant element of the list e (loop variable). They do not reference the same location in memory.

    Test your function using test_simulateMonth.py.

  6. Write a function to simulate a year

    The simulateYear function takes in the parameter list and population list. It calls calcSurvival, then it calls incrementAge, then it loops twelve times calling simulateMonth. Finally, it returns the population list. When calling each of the helper functions, pass in the population list and assign the result of the function back to the population list. For example:

        population = calcSurvival( parameters, population )

    Test your function using test_simulateYear.py. It will ensure that the basic functionality is present. We will test it more thoroughly once more functions have been written.

  7. Write a function to calculate statistics on the simulation

    The calcResults function calculates how many calves, juveniles, adult males, adult females, and seniors are in the population. It then returns a list with those values in it, along with the total number in the population, and the number culled from the population that year. It takes as input the parameters, the population list, and the number of animals that were just culled from the population.

    Get the juvenile age and max age parameters. Initialize variables to hold the number of calves, juveniles, adult males, adult females, and seniors. Then loop over the population list. For each elephant, increment the appropriate variable (use if statements to figure out the category for each elephant). When the loop is complete, return a list with the following items: the total population size, the number of calves, the number of juveniles, the number of adult males, the number of adult females, the number of seniors, and the number of animals culled.

    Test your function using test_calcResults.py.

  8. Run a simulation

    The runSimulation function takes two parameters: the parameter list and the number of years, N, to run the simulation. Then it creates the new population, applies any control procedures (e.g. darts them), loops over N years, simulating the year, and it keeps track of the demographics for each year by appending them to a list. Read the following code to make sure you understand it, then copy-paste it into elephant.py.

    Note, you may need to add a parameter to your parameter list (and indexes) that specifies the number of years to run the simulation. The default number of years to simulation is 200. Use IDXNumYears for the index.

    def runSimulation(parameters):
        popsize = parameters[IDXCarryingCapacity]
    
        # init the population
        population = initPopulation( parameters )
        [population,numCulled] = controlPopulation( parameters, population )
    
        # run the simulation for N years, storing the results
        results = []
        for i in range(parameters[IDXNumYears]):
            population = simulateYear( parameters, population )
            [population,numCulled] = controlPopulation( parameters, population )
            results.append( calcResults( parameters, population, numCulled ) )
            if results[i][0] > 2 * popsize or results[i][0] == 0 : # cancel early, out of control
                print( 'Terminating early' )
                break
            
        return results 
  9. Write your main function

    Create your main function. It should take argv (list of strings from the command line) as an argument. The only command line argument you will need is the probability of darting.

    The first part of your main function should be a usage statement, then assign the probability of darting from the command line parameter float(argv[1]). Next, set up the rest of the parameters and make the parameter list (pretty much identical to your test code). Next, call runSimulation once and store the return value in a results list. Make sure you are modifying the probDart parameter based on the command line argument.

    Test your function and print out the last item in the results list. You may want to edit your runSimulation function so it prints out the total population value each year of the simulation.

    Finish your main function by calculating the average results (average total population, average number of calves, average number of juveniles, and so on). Print out those results nicely at the end of your main function.

  10. Run simulations to figure the optimal darting probability

    Use your simulation to figure out the percent of adult females that need to be darted in order to hold the population steady over 200 years of simulation time. A steady population is defined as being more than 6500 and less than 8000 at the end of the simulatoin.

  11. Run simulations and compare results

    Run simulations that use the culling population-control strategy (set the Percent Darted parameter to 0.0). Determine how the population demographics are different in this scenario compared to using contraception. Describe those differences. What are the average numbers of calves, juveniles, adult males, adult females, and seniors? Where are the most obvious differences in demographics between the two groups (think like a tourist).


Follow-up Questions

  1. What is the difference bewteen a tuple and a list?
  2. Why is it helpful to use a variable like IDXNumYears instead of a number (e.g. 3) when accessing a particular element of a list?
  3. What is the value of running a simulation like this?

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 Therese Donovan, University of Vermont.