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 between the two methods of limiting the population. Second, identify the probability with which adult females need 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.


calcSurvival( )

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 age and the three survival probabilities (calf, adult, senior).

Create an empty list, newPopulation. 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 newPopulation list. Do this by testing if a call to random.random() is less than the survival probability.

After the loop, return the newPopulation 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.

Following are the elephant age categories. Note that the probability of adult survival applies to both adults and juveniles.

Age CategoryAge in Years

dartElephants( )

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.

The first step is to get the three parameters into local variables. Then the function should loop over each elephant in the population list. Test if the elephant is a female older than the juvenile age and less than or equal to the maximum 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

cullElephants( )

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 (see below for the definition) 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 new 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

controlPopulation( )

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 "probability of darting" 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

simulateMonth( )

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, juvenile age, and maximum 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 / (CalvingInterval*12 - 22), where CalvingInterval stands for the value of the Calving Interval parameter for the elephant. (For example, our default value for it is 3.1.) <--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.

simulateYear( )

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.

calcResults( )

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 (in this order): 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.

runSimulation( )

The runSimulation() function takes the parameter list as input. Recall that one of the parameters was the number of years to run the simulation--create a variable IDXNumYears for the index of that parameter in the parameters list, so we can access that value for the main loop of this function, as shown below. The runSimulation() function should create the new population, apply any control procedures (e.g., darting them), loop over numYears years, simulating each year, and it keep 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.

def runSimulation(parameters):
    popsize = parameters[IDXCarryingCapacity]

    # init the population
    population = initPopulation( parameters )
    [population,numCulled] = controlPopulation( parameters, population )

    # run the simulation for numYears 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

main( )

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.

Population Control Analysis

With your code, you can run simulations to analyze data from two different population control strategies: culling; and using contraceptive darts.

  1. First, run simulations that use the culling population-control strategy (set the probability of darting parameter value to 0.0).
  2. Then, run simulations to figure out the probability with which adult females 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 darting probability did you find? What are the average numbers of calves, juveniles, adult males, adult females, and seniors over the simulated years? Be sure to include your answers to those questions in your writeup.
  3. Once you've run enough simulations of both the culling and darting strategies to generate data, determine how the population demographics (e.g., numbers in each of the demographic groups--calves, juveniles, etc.) are different in the culling scenario and the darting scenario. Where are the most obvious differences in demographics between the two groups? In your writeup, describe these differences and discuss the potential implications of these differences on tourists' observations of the park.


Each assignment will have a set of suggested extensions. The required tasks and writeup constitute about 83% of the assignment. If you do only the required tasks and writeup -- and to 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.

These are only examples to help you start thinking of the unlimited possible ways you could extend the project. You are strongly encouraged to design your own extensions to suit your interests and show off your computational thinking skills.

Whichever extensions you choose, be sure to discuss your motivation, design process, implementation, and results in the writeup. A screenshot of your results is usually a great idea.

Write Up & Hand In

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 cs152f18project5 in the label field on the bottom of the page. But give the page a meaningful title (e.g., Eric'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.

© 2019 Eric Aaron (with contributions from Colby CS colleagues).