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.
Tasks
- 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.
- 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
- 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
- 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
- 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.
- 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.
- 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.
- 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
- 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.
- 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.
- 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
- What is the difference bewteen a tuple and a list?
- 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?
- 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.
- 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.
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.
- Is your name at the top of each code file?
- Does every function have a comment or docstring specifying what it does?
- 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. Please include a header for each section.
- Abstract
A brief summary of the project, 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. An abstract should define the project's key lecture concepts in your own words for a general, non-CS audience. It should also describe the program's context and output, highlighting a couple of important algorithmic and/or scientific details.
Writing an effective abstract is an important skill. Consider the following questions while writing it.
- Does it describe the CS concepts of the project (e.g. writing well-organized and efficient code)?
- Does it describe the specific project application (e.g. extracting data)?
- Does it describe your the solution or how it was developed (e.g. what code did you write)?
- Does it describe the results or outputs (e.g. did your code work as expected and what did the results tell you)?
- Is it concise?
- Are all of the terms well-defined?
- Does it read logically and in the proper order?
- Methods
The method section should describe in clear sentences (without pasting any code) at least one example of your own computational thinking that helped you complete your project. This could involve illustrating how a key lecture concept was applied to creating an image, how you solved a challenging problem, or explaining an algorithmic feature that is essential to your program as well as why it is so essential. The explanation should be suitable for a general audience who does not know Python.
Your methods section should be at most one or two paragraphs.
- Results
Present your results in a clear manner using human-friendly images or graphs labeled with captions and interpreted for a general audience such as your peers not in the course. Explain, for a general, non-CS audience, what your output means and whether it makes sense.
- Extensions
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.
- Follow-up questions
The answers to any follow-up questions (there will be 3-4 for each project).
- Reflection
Draw connections between lecture concepts utilized in this project and real-world problems that interest you. How else could these concepts apply to our everyday lives? What are some specific things you had to learn or discover in order to complete the project?
- References/Acknowledgements
Identify your collaborators, 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. Cite any other sources, imported libraries, or tutorials you used to complete the project.
Thanks to Cathy Collins for the project idea and documentation. The original project concept and idea came from Therese Donovan, University of Vermont.