CS 152: Project 5

Title image Project 5
Spring 2017

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 Park, 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


  1. 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 which elephants survive to the next year. The function takes the parameter list and population list as arguments. The function uses the max adult age, and the three survival probabilities (calf, adult, senior).

    Create an empty list, new_population. Then loop over the existing population list. Use the age of the elephant to determine which survival probability applies, then use the survival probability to see if the elephant is 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, 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 maximum juvenile age, and the maximum adult age.

    The first step is to get the three values from the parameters list into local variables. Then the function should loop over each elephant in the population list. Test if the elephant is a female is an adult (older than the maximum juvenile age and less than or equal to the maximum adult age). If so, then test 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, cullElephants, that removes randomly chosen elephants from the population. The function takes in the parameter list and population list as arguments. It returns 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.

    The function should first store the carrying capacity entry in the parameters list to a local variable carryingCapacity, then 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 animals to be culled, then randomly shuffle the population list (use random.shuffle). Then, use the appropriate list-slicing code to keep the first carryingCapacity animals. Return a tuple containing this shuffled population list and then the number of animals culled.

    A tuple is like a list but uses parentheses instead of square brackets. So your return statement might look something like the following.

    return (newPopulation, numCulled)

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

  4. Write a function, controlPopulation, that will determine whether the population should be darted or culled. It then returns the new population list and the number culled (which will be zero, if the elephants were darted) as a tuple. Below are the comments that will help you write this 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. 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 when one is born. The function takes in the parameter list and the population list. It returns the population list. The function uses the calving interval, maximum juvenile age, and maximum adult age parameters.

    The first step is to get the three parameters into local variables. The second step is to 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, you need to 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. 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. The calcResults function calculates how many calves, juveniles, adult females, adult males, 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 max juvenile age and max adult age parameters. Initialize variables to hold the number of calves, juveniles, adult females, adult males, 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 females, the number of adult males, the number of seniors, and the number of animals culled.

    Test your function using test_calcResults.py.

  8. The runSimulation function takes in the parameter list. 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 append 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 value to your parameter list that specifies the number of years to run the simulation. The default number of years to simulation is 200. Use IDXNumYears for the indexi variable.

    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'
        return results
  9. Finally, 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.

    At this point, 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 up 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 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.
  11. 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 simulation. What are the average numbers of calves, juveniles, adult females, adult males, and seniors? Where are the most obvious differences in demographics between the two groups (think like a tourist).


Each assignment will have a set of suggested extensions. The required tasks constitute about 85% of the assignment, and if you do only the required tasks and do them well you will earn a B+. To earn a higher grade, you need to undertake one or more extensions. The difficulty and quality of the extension or extensions will determine your final grade for the assignment. One complex extension, done well, or 2-3 simple extensions are typical.

Write-up and Hand-in

Do all of your functions have docstrings? Before turning in code, double-check your functions for docstrings. Each function must have a docstring that says what the purpose of the function is, what each parameter is for (i.e. the type of value it is expected to have and how it will be used by the code), and what the return value is (if there is one). Also, be sure to place this docstring as the first (indented) item in th ebody of the function (not above the function).

Turn in your code by putting it into your private hand-in directory on the Courses server. All files should be organized in a folder titled "Project 5" and you should include only those files necessary to run the program. We will grade all files turned in, so please do not turn in old, non-working, versions of files.

Make a new wiki page for your assignment. Put the label cs151s17project5 in the label field on the bottom of the page. But give the page a meaningful title (e.g. Milo's Project 5).

In general, your intended audience for your write-up is your peers not in the class. 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. Follow the outline below.

Thanks to Cathy Collins for the project idea and documentation. The original project concept and idea came from Therese Donovan, University of Vermont.