## Objectives

This is the second project on elephant population simulation. In the first project, you developed the overall simulation and used it to figure out a single parameter: the probability with which female elephants are darted each year. This week we're going to explore how to optimize one or more parameters of a simulation automatically. This will involve a little bit of restructuring of your elephant simulation code and then the development of an optimizer that will run the simulation many times in order to figure out parameters that best achieve a specific outcome.

### Project 5

Projects 5-7 are cumulative. If your Project 5 generated reasonable results, you're ready to go! But if your results were off for Project 5, the same bug will throw your results in Project 6 as well. Please stop by office hours if you're stuck. We're here to help!

### optimize( )

Create a new file called optimize.py. Have the file import the sys, elephant, and random packages. Then create a function called optimize() with the following definition.

```# Executes a search to bring the result of the function optfunc to zero.
# min: minimum parameter value to search
# max: maximum parameter value to search
# optfunc: function to optimize
# parameters: optional parameter list to pass to optfunc
# tolerance: how close to zero to get before terminating the search
# maxIterations: how many iterations to run before terminating the search
# verbose: whether to print lots of information or not
def optimize( min, max, optfunc, parameters = None, tolerance = 0.001, maxIterations = 20, verbose=False ):
```

The optimize() function is very similar to the binary search function you wrote in lab. Start by assigning to a variable done the value False. Then, start a loop that executes while done is False.

Inside the loop:

1. Assign to testValue the average of max and min. This is not (should not be) an integer calculation.
2. If verbose is True, print out testValue.
3. Assign to result the return value of calling optfunc() with testValue and parameters as the arguments.
4. If verbose is True, print out the result value.
5. If the result is positive, assign to max the value of testValue. Else if result is negative, assign to min the value of testValue. Else, assign to done the value True.
6. If `max - min` is less than the tolerance value, then assign to done the value True.
7. Decrement maxIterations. If maxIterations is less than or equal to zero, then set done to True.

Outside the loop, return testValue.

To test your optimize() function, copy the following code and run optimize.py. As noted in the comments, try making tolerance smaller and see if it matches more digits in the target value.

```# a function that returns x - target
def target(x, pars):
return x - 0.73542618

# Tests the binary search using a simple target function.
# Try changing the tolerance to see how that affects the search.
def testTarget():
res = optimize( 0.0, 1.0, target, tolerance = 0.01, verbose=True)
print(res)

if __name__ == "__main__":
testTarget()
```

### Testing

The next step is to test the optimize() function with your elephantSim() function. Create a testEsim() function (similar to testTarget() above) that calls optimize() with a min value of 0.0, a max value of 0.5, and passes it elephant.elephantSim as the target function. You probably want to set `verbose=True` as well. As with the testTarget() function, assign the return value to a variable and then print the variable. At the bottom of your code, change `testTarget()` to `testEsim()`, then run optimize.py. Does your optimize function find a value close to 0.43 for the darting probability?

### evalParameterEffect( )

The next step is to automate the process of evaluating the effects of changing a simulation parameter across a range of values. This function will let us discover, for example, the effect on the darting probability of changing the calfSurvival rate from 80% to 90% in steps of 1%. The function definition is given below.

```# Evaluates the effects of the selected parameter on the darting probability
# whichParameter: the index of the parameter to test
# testmin: the minimum value to test
# testmax: the maximum value to test
# teststep: the step between parameter values to test
# defaults: default parameters to use (default value of None)
def evalParameterEffect( whichParameter, testmin, testmax, teststep, defaults=None, verbose=False ):

# if defaults is None, assign to simParameters the result of calling elephant.defaultParameters.
# else, assign to simParameters a copy of defaults  (e.g., simParameters = defaults[:])

# create an empty list (e.g., results) to hold the results

if verbose:
print("Evaluating parameter {0:d} from {1:.3f} to {2:.3f} with step {3:.3f}".format(whichParameter, testmin, testmax, teststep))

# assign to t the value testmin
# while t is less than testmax
# assign to the whichParameter element of simParameters (i.e., simParameters[whichParameter]) the value t
# assign to probDart the result of calling optimize with the appropriate arguments, including simParameters
# append to results the tuple (t, probDart)
if verbose:
print("{0:8.3f} \t{1:8.3f}".format(t, probDart))
# increment t by the value teststep

if verbose:
print("Terminating")

# return the list of results
```

Test your evalParameterEffect() function by modifying your top level code at the bottom of your file to be the following.

```if __name__ == "__main__":
evalParameterEffect( elephant.IDXProbAdultSurvival, 0.98, 1.0, 0.001, verbose=True )
```

What does this do? What should you expect the output to be?

### Analysis

Your final task is to make the following evaluations, showing the effect on the darting probability of the following parameter sweeps. Make a table or graph (or both) for each case. These five items should go in your writeup.

1. Vary the adult survival probability from 0.98 to 1.0 in steps of 0.001.
2. Vary the calf survival probability from 0.80 to 0.90 in steps of 0.01.
3. Vary the senior survival probability from 0.1 to 0.5 in steps of 0.05.
4. Vary the calving interval from 3.0 to 3.4 in steps of 0.05.
5. Vary the carrying capacity from 3500 to 7000 in steps of 500.

Your goal is to automate this process as much as possible. One option is to write a program that executes all of these in sequence and writes the results to a CSV file. Note that this option may take a while to test and debug. A second option is to make a program that runs only one of these at a time, but it lets you control it from the command line (specifying which parameter and the min/max/step). That is easier to debug, but you have to run it several times.

It is probably a good idea to figure out how to write data out to a CSV file and use that capability here.

```fp = open( "filename here", "w") # open a file for writing
fp.write( "write this string to the file\n")  # write a string to the file
fp.close()  # close the file
```

Explain each of your results in the writeup. In each case, you may find that addressing the following questions helps you get started:

• Do the results make sense?
• Why or why not?
• Why does the darting probability go up or stay constant when you vary the parameter?
If you observe any other interesting parameter effects, e.g., an impact on population demographics, discuss those as well.

### Extensions

Each assignment will have a set of suggested extensions. The required tasks constitute about 83% 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.

The following are a few examples of potential extensions. Please do not feel that your extensions must be drawn from this list. Get creative, design extensions that interest you, and explain why they are awesome when you present the results in your writeup. Your interest in your own extensions really does make a difference.

• Figure out how to automate the graphing process using gnuplot.
• Have your program write out proper CSV files with a header line and appropriate commas.
• How much variation is there in the average total population for a 200-year elephant simulation across different runs? How stable is the estimate generated by doing 5 simulation runs?
• 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?

### Writeup

Make a new wiki page for your assignment. Put the label `cs152f18project6` on the page. Each of you needs to make your own writeup.

In addition to making the wiki page writeup, put the python files you wrote on the Courses server in your private directory in a folder named project6.