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.
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, 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.
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 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
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 list containing
first the new population list and second the number of elephants
culled. The function makes use of the carrying capacity of the
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
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
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 / (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.
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.
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
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.
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
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 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] > 2 * popsize or results[i] == 0 : # cancel early, out of control print 'Terminating early' break return results
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). 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.
- 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.
- 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. 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).
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.
- Does the percent to be darted change with different carrying capacities?
- How sensitive is the system to changes in various variables? For example, modify the calf survival rate to 80% or up to 90% and see how that modifies the culling rate or the percent darted rate.
- Use your stats.py file from Project 3 to compute the average values of each demographic. The list of results that you accrue when running multiple simulations can be thought of as the contents of a spreadsheet. Each list is a row, and each element in the list belongs in a column. The first column is for the total population, the second column is for the count of the calf population, etc. You may want to add a function that rearranges data from lists of rows to lists of columns, then you will be able to use your existing functions in stats.py to compute the average value of a given column.
- Write the population data (or direct it) to a CSV file and generate plots of total population--or demographic subsets--under different scenarios. If you want a fun challenge, try using gnuplot to generate the plots.
- Implement a flag-based command line parameter system, allowing the user to set any or all parameters on the command line.
Write-up and 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 cs151f16project5 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.
- A brief summary of the task, in your own words. This should be no more than a few sentences. Give the reader context and identify the key purpose of the assignment.
- A description of your solution to the tasks, including any text output or images you created. This should be a description of the form and functionality of your final code. Note any unique computational solutions you developed or any insights you gained from your code's output. You may want to incorporate code snippets in your description to point out relevant features. Code snippets should be small segments of code--usually less than a whole function--that demonstrate a particular concept. If you find yourself including more than 5-10 lines of code, it's probably not a snippet.
- A description of any extensions you undertook, including text output or images demonstrating those extensions. If you added any modules, functions, or other design components, note their structure and the algorithms you used.
- A brief description (1-3 sentences) of what you learned. Think about the answer to this question in terms of the stated purpose of the project. What are some specific things you had to learn or discover in order to complete the project?
- A list of people you worked with, including TAs and professors. Include in that list anyone whose code you may have seen, such as those of friends who have taken the course in a previous semester.
- Double-check the label. When you created the page, you should have added a the label cs151f16project5. Make sure it is there.
Thanks to Cathy Collins for the project idea and documentation. The original project concept and idea came from Therese Donovan, University of Vermont.