CS 152: Project 7

Project 7: Object-Oriented Simulation Design

This is the third project on elephant population simulation. In the
first project, you developed the overall simulation and used it to
figure out a single parameter: the percentage of female elephants to
dart each year. In the second project, you explored how to optimize
one or more parameters of a simulation automatically. In this project,
you are using the same content/concept but redesigning the code to use
classes for the Elephant and Simulation parts of the project. You
should find that using classes makes the coding process simpler and
avoids some of the challenges of the prior two weeks.

Tasks

Create a new file, simulation.py. At the top, import sys,
random, and elephant. Then begin a new
class called Simulation. The first thing to write is the __init__ method. Using
the following def statement for it. Note that numYears
will no longer be part of the Simulation fields. It will
be provided as a parameter when runSimulation is
called.

In the body, assign each of these values to a corresponding field of
of the object (self). For example, the following assigns the percDart
parameter to a corresponding field of the object, which is referred to
by the variable self.

self.percDart = percDart

In addition, create fields to hold the population and the results and
assign an empty list to each of them. That completes the creation of
the Simulation object and it has all of the variables it needs to
hold. All of your code in the __init__ method will be assignments to
fields.

The next step is to create the "get" and "set"
functions, one for each of the fields except for population.
For example, the following returns the value of the percDart field.

def getPercDart(self):
return self.percDart

Create similar functions for all of the other simulation parameters.
Then create functions that allow you to set each parameter. Each of
these functions should take self and the new value of the field as
arguments. For example, the following sets the percDart field to a
new value.

def setPercDart(self, val):
self.percDart = val

When you have both get and set functions, test them with the following
test program, which should give
you this output. Note that it
doesn't matter what you call your internal fields. You could store
carrying capacity in self.cc, for example. It does
matter what you call the get and set functions: they have to match
what is in the test code.

The next task is to recreate your simulation from project 5 using the
same breakdown into methods. The differences will be in how you
access the information. Your simulation parameters will always be
contained in the fields of the object that you created in the init
method (e.g. self.carryingCapacity). Likewise, to call a
method of the object, such as initPopulation, you will use
self.initPopulation().

Start with the initPopulation method, which should have the following
definition.

def initPopulation(self)

Make sure the def is inside the Simulation class definition (tabbed in
by one level). The initPopulation method should assign the empty list
to self.population, then it should loop for the number of times
specified by the carryingCapacity parameter (self.carryingCapacity)
and, each time through the loop, append a new elephant to the
population (self.population).

In order to create a new elephant, you need to create a new Elephant
object. As with all objects, use the name of the class as a function
to create a new instance of the object. Since the Elephant.__init__ method
uses calvingInterval, pass that parameter as the sole argument to
Elephant. The following expression is how you create a new Elephant
object. This is what you should be appending to self.population.

elephant.Elephant( self.calvingInterval )

Write a new method, showPopulation that prints out a header
line (something that might print a separator, or the string "Showing
population"), then loops through the population and, for each
elephant, prints it. Because we created a __str__ method in the
Elephant class, if the variable e is a reference to an
Elephant object, you can use print e to print it to the
terminal. The header for showPopulation should be:>

def showPopulation(self):

Write the incrementAge method that loops over the population
list and, for each Elephant object, calls its incrementAge method.
Yes, this is a two line function.

At this point, you may want to add a simple test function, as below,
to the bottom of your simulation.py file. This function should not be
part of the Simulation class.

Run your simulation.py file and make sure it is printing out a
population of 20 elephants and that it is correctly incrementing their
ages.

Write the dartPopulation method that loops over the population
list and, for each Elephant object, if it is female (use the isFemale
method), and if random.random() is less than self.percDart, call the
dart method for the elephant object. Update your test function so that
it calls dartPopulation and then shows the population again.

Write a method cullElephants_0 that implements the same culling
method as the prior project. It should test if there are more
elephants than the carrying capacity. If so, it should reduce the
population list to the carrying capacity. Finally, it should return
the number of elephants culled. Note that it does not need to return
the population. But you do need to make sure that self.population
holds the new, modified population list. You can test this by adding
the following to your simple test function.

Write the controlPopulation method. It should call either
cullElephants_0 or dartPopulation, depending on the value of
self.percDart. Then it needs to return the number of elephants
culled, which is either 0, if dartPopulation was called, or the return
value of cullElephants_0.

Write the simulateMonth method. This will be much simpler than
the prior week. It should loop over the population. For each
elephant, if it is female and an adult (use isFemale and isAdult),
then if the return value of the elephant's progressMonth method is
True, append a new Elephant of age 1 to the population.

Write the calcSurvival method. Assign the empty list to a
temporary population variable. Then loop over the population
(self.population). Test each calf, juvenile/adult, or senior and, if
they survive, append them to the new population. The last step should
be to assign to self.population the new population. The method does
not return anything.

Write the simulateYear method. This will be four lines of
code. Call the calcSurvival method, call the incrementAge method,
then loop 12 times and inside the loop call the simulateMonth method.

Write the calcResults method. It should do pretty much the
same thing as it did in the prior two labs. The calcResults method
should have self and numCull as its parameters. It should return a
list with total population, 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 culled.

Finally, write the runSimulation method. It should
have self and numYears as parameters. It should do
the same thing as in the prior labs: call initPopulation, call
controlPopulation, assign an empty list to self.results, then loop for
the number of years (numYears). Each time through the loop it
should call simulateYear, then controlPopulation, then append to the
results list the return value of calcResults. Make sure to use
self. whenever you are referencing a field of the object
(e.g. results) and whenever you are calling a method of the Simulation
class (e.g. calcResults). The method should return self.results.

You can use this test function to
test your runSimulation method. When you run it with a value like
0.42, you should get around 1000 total elephants at the end. If you
run it with larger or smaller percdart values, make sure you get
smaller/larger total population results.

Write a writeDemographics function, that takes in a filename
(in addition to self) and writes the results, stored in self.results,
to a proper CSV file. The CSV file should have a header line that
begins with a hash, #, and then has a header for each column. Make
Year the first column, then total population, number of calves, number
of juveniles, number of male adults, number of female adults, number
of seniors, and number culled.

You can use the following test
function to run your whole simulation. The test file creates the
file demographics.csv. Note, it will overwrite any existing file with
that name.

Make a plot that shows year, number of calves, number of juveniles,
and number of adult females for a dart percentage of 0.425. Then
make a second plot that shows the same data for a dart percentage of
0.0.

Implement a second cull strategy that culls only adult females.
Create a new function cullElephants_1 that implements the strategy.
The modify controlPopulation so that the new strategy is called
instead of cullElephants_0 when the cullStrategy is equal to 1.

Run the two different culling strategies, then examine the
demographics and number culled, in comparison to random culling. You
can present this as a simple table showing the average number of each
category (calf, junvenile, adult female, etc) for each case.

Update the code so that you can simulate the population under normal
conditions, then simulate a catastrophic event that kills off a
percentage of the population, then continue with the population
simulation.

First, write a method to simulate a catastrophic event: decimate.
The decimate method reduces the population by the given
percent. The surviving elephants should be a random sampling of the
population.

Second, add a parameter to runSimulation that makes it
possible to skip the inititialization steps. i.e. change the def statement to def
runSimulation(self, numYears, startFresh = True) and adjust
your code so that it does the initialization (calling initPopulation,
calling controlPopulation, and assigning an empty list to self.results)
only if startFresh is True.

Use these methods to run the simulation for 100 years (starting
fresh), then decimate the population by 30%, then continue the
simulation for another 100 years. How do the culling v. darting
strategies respond?

Extensions

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.

Figure out how to automate the graphiing process using gnuplot.

Develop another management strategy, which is to adjust the percent
darted based on whether the population is above or below the target.
You have to make very small adjustments to the percent darted in order
to avoid large oscillations. See how this method responds to an event
that decimates the population.

Explore how the population responds to decimation events that are
selective in their effect. For example, what if only calves and
juveniles are affected? What if only pregnant females are affected?

Develop other culling/darting strategies and discuss their effects and
trade-offs. How easy would they be to implement?

Enable the user to control your top level program with optional flags.
For example, -par CarryingCapacity would specify that the program
should evaluate carrying capacity, and -min 3500 would specify that it
should start the evaluation at 3500.

Check out the os package (import os). What could you do with the
os.system function to automate your simulations?

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 7" 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 cs152f16project7 in the label field on the
bottom of the page. But give the page a meaningful title (e.g. Milo's
Project 7).

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 summary of your findings in the simulations. What did you discover
by using the simulation? Do your results make sense?

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 cs152f16project7. Make sure it is there.