<jupyter_start><jupyter_text>Iteration *Modeling and Simulation in Python*Copyright 2021 <NAME>License: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)<jupyter_code># install Pint if necessary

try:
    import pint
except ImportError:
    !pip install pint
# download modsim.py if necessary

from os.path import basename, exists

def download(url):
    filename = basename(url)
    if not exists(filename):
        from urllib.request import urlretrieve
        local, _ = urlretrieve(url, filename)
        print('Downloaded ' + local)
    
download('https://raw.githubusercontent.com/AllenDowney/' +
         'ModSimPy/master/modsim.py')
# import functions from modsim

from modsim import *<jupyter_output><empty_output><jupyter_text>To paraphrase two Georges, "All models are wrong, but some models aremore wrong than others." This chapter demonstrates the process weuse to make models less wrong.As an example, we'll review the bikeshare model from the previouschapter, consider its strengths and weaknesses, and gradually improveit. We'll also see ways to use the model to understand the behavior ofthe system and evaluate designs intended to make it work better. This chapter is available as a Jupyter notebook where you can read the text, run the code, and work on the exercises. Click here to access the notebooks: .  Iterative ModelingThe model we have so far is simple, but it is based on unrealisticassumptions. Before you go on, take a minute to review the model fromthe previous chapter. What assumptions is it based on? Make a list ofways this model might be unrealistic; that is, what are the differences between the model and the real world?Here are some of the differences on my list:-   In the model, a student is equally likely to arrive during any    one-minute period. In reality, this probability varies depending on time of day, day of the week, etc.-   The model does not account for travel time from one bike station to another.-   The model does not check whether a bike is available, so it's    possible for the number of bikes to be negative (as you might have    noticed in some of your simulations). Some of these modeling decisions are better than others. For example,the first assumption might be reasonable if we simulate the system for a short period of time, like one hour.The second assumption is not very realistic, but it might not affect the results very much, depending on what we use the model for.On the other hand, the third assumption seems problematic, and it isrelatively easy to fix. In this chapter, we will.This process, starting with a simple model, identifying the mostimportant problems, and making gradual improvements, is called*iterative modeling*.For any physical system, there are many possible models, based ondifferent assumptions and simplifications. It often takes severaliterations to develop a model that is good enough for the intendedpurpose, but no more complicated than necessary.  More Than One State ObjectBefore we go on, I want to make a few changes to the code from theprevious chapter. First I'll generalize the functions we wrote so theytake a `State` object as a parameter. Then, I'll make the code morereadable by adding documentation.Here is one of the functions from the previous chapter, `bike_to_wellesley`:<jupyter_code>def bike_to_wellesley():
    bikeshare.olin -= 1
    bikeshare.wellesley += 1<jupyter_output><empty_output><jupyter_text>When this function is called, it modifies `bikeshare`. As long as thereis only one `State` object, that's fine, but what if there is more thanone bike share system in the world? Or what if we want to run more thanone simulation?This function would be more flexible if it took a `State` object as aparameter. Here's what that looks like:<jupyter_code>def bike_to_wellesley(state):
    state.olin -= 1
    state.wellesley += 1<jupyter_output><empty_output><jupyter_text>The name of the parameter is `state`, rather than `bikeshare`, as areminder that the value of `state` could be any `State` object, not just the one we called `bikeshare`.This version of `bike_to_wellesley` requires a `State` object as aparameter, so we have to provide one when we call it:<jupyter_code>bikeshare = State(olin=10, wellesley=2)
bike_to_wellesley(bikeshare)<jupyter_output><empty_output><jupyter_text>Again, the argument we provide gets assigned to the parameter, so thisfunction call has the same effect as:```state = bikeshare state.olin -= 1 state.wellesley += 1```Now we can create as many `State` objects as we want:<jupyter_code>bikeshare1 = State(olin=10, wellesley=2)
bikeshare2 = State(olin=2, wellesley=10)<jupyter_output><empty_output><jupyter_text>And update them independently:<jupyter_code>bike_to_wellesley(bikeshare1)
bike_to_wellesley(bikeshare2)<jupyter_output><empty_output><jupyter_text>Changes in `bikeshare1` do not affect `bikeshare2`, and vice versa. Sowe can simulate different bike share systems, or run multiplesimulations of the same system.  DocumentationAnother problem with the code we have so far is that it contains no*documentation*.Documentation is text we add to a program to helpother programmers read and understand it. It has no effect on theprogram when it runs.There are two kinds of documentation, *docstrings* and *comments*:* A docstring is a string in triple quotes that appears at the beginning of a function.* A comment is a line of text that begins with a hash symbol, ``.Here's a version of `bike_to_olin` with a docstring and a comment.<jupyter_code>def bike_to_olin(state):
    """Move one bike from Wellesley to Olin.
    
    state: bikeshare State object
    """
    # We decrease one state variable and increase the
    # other so the total number of bikes is unchanged.
    state.wellesley -= 1
    state.olin += 1<jupyter_output><empty_output><jupyter_text>Docstrings follow a conventional format:-   The first line is a single sentence that describes what the function does.-   The following lines explain what the parameters are.A function's docstring should include the information someone needs toknow to *use* the function; it should not include details about how the function works.Comments provide details about how the function works, especially if there is something that would not be obvious to someone reading the program.  Negative BikesThe changes we've made so far improve the quality of the code, but wehaven't done anything to improve the quality of the model. Let's do that now.Currently the simulation does not check whether a bike is available when a customer arrives, so the number of bikes at a location can benegative. That's not very realistic.Here's a version of `bike_to_olin` that fixes the problem:<jupyter_code>def bike_to_olin(state):
    if state.wellesley == 0:
        return
    state.wellesley -= 1
    state.olin += 1<jupyter_output><empty_output><jupyter_text>The first line checks whether the number of bikes at Wellesley is zero. If so, it uses a *return statement*, which causes the function to end immediately, without running the rest of the statements. So if there are no bikes at Wellesley, we return from `bike_to_olin` without changing the state.We can test it by initializing the state with no bikes at Wellesley And calling `bike_to_olin`.<jupyter_code>bikeshare = State(olin=12, wellesley=0)
bike_to_olin(bikeshare)<jupyter_output><empty_output><jupyter_text>The state of the system should be unchanged.<jupyter_code>show(bikeshare)<jupyter_output><empty_output><jupyter_text>No more negative bikes (at least at Wellesley).  Comparison OperatorsThe updated version of `bike_to_olin` uses the equals operator, `==`, which compares two values and returns `True`, if they are equal, and `False` otherwise.It is easy to confuse the equals operator with the assignment operator, `=`, which assigns a value to a variable. For example, the following statement creates a variable, `x`, if it doesn't already exist, and gives it the value `5`.<jupyter_code>x = 5<jupyter_output><empty_output><jupyter_text>On the other hand, the following statement checks whether `x` is `5` andreturns `True` or `False`. It does not create `x` or change its value.<jupyter_code>x == 5<jupyter_output><empty_output><jupyter_text>You can use the equals operator in an `if` statement, like this:<jupyter_code>if x == 5:
    print('yes, x is 5')<jupyter_output><empty_output><jupyter_text>If you make a mistake and use `=` in an `if` statement, like this:```if x = 5:    print('yes, x is 5')```That's a *syntax error*, which means that the structure of the program is invalid. Python will print an error message and the program won't run. The equals operator is one of Python's *comparison operators*. The othersare:| Operation             	| Symbol 	||-----------------------	|--------	|| Less than             	| `<`      	|| Greater than          	| `>`      	|| Less than or equal    	| `<=`     	|| Greater than or equal 	| `>=`     	|| Equal                 	| `==`     	|| Not equal             	| `!=`     	|  MetricsGetting back to the bike share system, at this point we have the ability to simulate the behavior of the system. Since the arrival of customers is random, the state of the system is different each time we run asimulation. Models like this are called random or *stochastic*; modelsthat do the same thing every time they run are *deterministic*.Suppose we want to use our model to predict how well the bike sharesystem will work, or to design a system that works better. First, wehave to decide what we mean by "how well" and "better".From the customer's point of view, we might like to know the probability of finding an available bike. From the system-owner's point of view, we might want to minimize the number of customers who don't get a bike when they want one, or maximize the number of bikes in use. Statistics like these that quantify how well the system works are called *metrics*.As an example, let's measure the number of unhappy customers.Here's a version of `bike_to_olin` that keeps track of the number ofcustomers who arrive at a station with no bikes:<jupyter_code>def bike_to_olin(state):
    if state.wellesley == 0:
        state.wellesley_empty += 1
        return
    state.wellesley -= 1
    state.olin += 1<jupyter_output><empty_output><jupyter_text>If a customer arrives at the Wellesley station and finds no bikeavailable, `bike_to_olin` updates `wellesley_empty`, which counts thenumber of unhappy customers.This function only works if we initialize `wellesley_empty` when wecreate the `State` object, like this:<jupyter_code>bikeshare = State(olin=12, wellesley=0, 
                  wellesley_empty=0)<jupyter_output><empty_output><jupyter_text>We can test it by calling `bike_to_olin`:<jupyter_code>bike_to_olin(bikeshare)<jupyter_output><empty_output><jupyter_text>After this update, there should be 12 bikes at Olin, no bikes at Wellesley, and one unhappy customer.<jupyter_code>show(bikeshare)<jupyter_output><empty_output><jupyter_text>Looks good!  SummaryIn this chapter, we wrote several versions of `bike_to_olin`:* We added a parameter, `state`, so we can work with more than one `State` object.* We added a docstring that explains how to use the function and a comment that explains how it works.* We used a conditional operator, `==`, to check whether a bike is available, in order to avoid negative bikes.* We added a state variable, `wellesley_empty`, to count the number of unhappy customers, which is a metric we'll use to quantify how well the system works.In the exercises, you'll update `bike_to_wellesley` the same way and test it by running a simulation.  Exercises Here's the code we have so far, with docstrings, all in one place.<jupyter_code>def run_simulation(state, p1, p2, num_steps):
    """Simulate the given number of time steps.
    
    state: State object
    p1: probability of an Olin->Wellesley customer arrival
    p2: probability of a Wellesley->Olin customer arrival
    num_steps: number of time steps
    """
    results = TimeSeries()
    results[0] = state.olin
    
    for i in range(num_steps):
        step(state, p1, p2)
        results[i+1] = state.olin
        
    results.plot(label='Olin')
    decorate(title='Olin-Wellesley Bikeshare',
             xlabel='Time step (min)', 
             ylabel='Number of bikes')
def step(state, p1, p2):
    """Simulate one time step.
    
    state: bikeshare State object
    p1: probability of an Olin->Wellesley ride
    p2: probability of a Wellesley->Olin ride
    """
    if flip(p1):
        bike_to_wellesley(state)
    
    if flip(p2):
        bike_to_olin(state)
def bike_to_olin(state):
    """Move one bike from Wellesley to Olin.
    
    state: bikeshare State object
    """
    if state.wellesley == 0:
        state.wellesley_empty += 1
        return
    state.wellesley -= 1
    state.olin += 1
def bike_to_wellesley(state):
    """Move one bike from Olin to Wellesley.
    
    state: bikeshare State object
    """
    state.olin -= 1
    state.wellesley += 1<jupyter_output><empty_output><jupyter_text>Exercise 1 Modify `bike_to_wellesley` so it checks whether a bike is available at Olin.  If not, it should add one to `olin_empty`.To test it, create a `State` that initializes `olin` and `olin_empty` to `0`, run `bike_to_wellesley`, and check the result.<jupyter_code># Solution goes here
# Solution goes here
# Solution goes here
# Solution goes here<jupyter_output><empty_output><jupyter_text>Exercise 2 Now run the simulation with parameters `p1=0.3`, `p2=0.2`, and `num_steps=60`, and confirm that the number of bikes is never negative.Start with this initial state:<jupyter_code>bikeshare = State(olin=10, wellesley=2,
                  olin_empty=0, wellesley_empty=0)
# Solution goes here<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.0725526999711233, 'fraction_numerical': 0.006569448455096737, 'mean_word_length': 4.3756305781916955, 'pattern_counts': {'":': 0, '<': 84, '<?xml version=': 0, '>': 88, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '20', 'dir': 'jupyter-structured-clean-dedup', 'id': '115950', 'license': 'MIT', 'n_tokens_mistral': 4020, 'n_tokens_neox': 3856, 'n_words': 1750, 'path': 'chapters/chap03.ipynb', 'repo_name': 'ms2144/ModSimPy', 'size': '13858'}