{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Modeling and Simulation in Python\n", "\n", "Chapter 3\n", "\n", "Copyright 2017 Allen Downey\n", "\n", "License: [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0)\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# Configure Jupyter so figures appear in the notebook\n", "%matplotlib inline\n", "\n", "# Configure Jupyter to display the assigned value after an assignment\n", "%config InteractiveShell.ast_node_interactivity='last_expr_or_assign'\n", "\n", "# import functions from the modsim library\n", "from modsim import *\n", "\n", "# set the random number generator\n", "np.random.seed(7)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## More than one State object\n", "\n", "Here's the code from the previous chapter, with two changes:\n", "\n", "1. I've added DocStrings that explain what each function does, and what parameters it takes.\n", "\n", "2. I've added a parameter named `state` to the functions so they work with whatever `State` object we give them, instead of always using `bikeshare`. That makes it possible to work with more than one `State` object." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "def step(state, p1, p2):\n", " \"\"\"Simulate one minute of time.\n", " \n", " state: bikeshare State object\n", " p1: probability of an Olin->Wellesley customer arrival\n", " p2: probability of a Wellesley->Olin customer arrival\n", " \"\"\"\n", " if flip(p1):\n", " bike_to_wellesley(state)\n", " \n", " if flip(p2):\n", " bike_to_olin(state)\n", " \n", "def bike_to_wellesley(state):\n", " \"\"\"Move one bike from Olin to Wellesley.\n", " \n", " state: bikeshare State object\n", " \"\"\"\n", " state.olin -= 1\n", " state.wellesley += 1\n", " \n", "def bike_to_olin(state):\n", " \"\"\"Move one bike from Wellesley to Olin.\n", " \n", " state: bikeshare State object\n", " \"\"\"\n", " state.wellesley -= 1\n", " state.olin += 1\n", " \n", "def decorate_bikeshare():\n", " \"\"\"Add a title and label the axes.\"\"\"\n", " decorate(title='Olin-Wellesley Bikeshare',\n", " xlabel='Time step (min)', \n", " ylabel='Number of bikes')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And here's `run_simulation`, which is a solution to the exercise at the end of the previous notebook." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def run_simulation(state, p1, p2, num_steps):\n", " \"\"\"Simulate the given number of time steps.\n", " \n", " state: State object\n", " p1: probability of an Olin->Wellesley customer arrival\n", " p2: probability of a Wellesley->Olin customer arrival\n", " num_steps: number of time steps\n", " \"\"\"\n", " results = TimeSeries() \n", " for i in range(num_steps):\n", " step(state, p1, p2)\n", " results[i] = state.olin\n", " \n", " plot(results, label='Olin')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can create more than one `State` object:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "bikeshare1 = State(olin=10, wellesley=2)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "bikeshare2 = State(olin=2, wellesley=10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Whenever we call a function, we indicate which `State` object to work with:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "bike_to_olin(bikeshare1)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "bike_to_wellesley(bikeshare2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And you can confirm that the different objects are getting updated independently:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "bikeshare1" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "bikeshare2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Negative bikes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the code we have so far, the number of bikes at one of the locations can go negative, and the number of bikes at the other location can exceed the actual number of bikes in the system.\n", "\n", "If you run this simulation a few times, it happens often." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "bikeshare = State(olin=10, wellesley=2)\n", "run_simulation(bikeshare, 0.4, 0.2, 60)\n", "decorate_bikeshare()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can fix this problem using the `return` statement to exit the function early if an update would cause negative bikes." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "def bike_to_wellesley(state):\n", " \"\"\"Move one bike from Olin to Wellesley.\n", " \n", " state: bikeshare State object\n", " \"\"\"\n", " if state.olin == 0:\n", " return\n", " state.olin -= 1\n", " state.wellesley += 1\n", " \n", "def bike_to_olin(state):\n", " \"\"\"Move one bike from Wellesley to Olin.\n", " \n", " state: bikeshare State object\n", " \"\"\"\n", " if state.wellesley == 0:\n", " return\n", " state.wellesley -= 1\n", " state.olin += 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now if you run the simulation again, it should behave." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "bikeshare = State(olin=10, wellesley=2)\n", "run_simulation(bikeshare, 0.4, 0.2, 60)\n", "decorate_bikeshare()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Comparison operators" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `if` statements in the previous section used the comparison operator `<`. The other comparison operators are listed in the book.\n", "\n", "It is easy to confuse the comparison operator `==` with the assignment operator `=`.\n", "\n", "Remember that `=` creates a variable or gives an existing variable a new value." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "x = 5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Whereas `==` compared two values and returns `True` if they are equal." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "x == 5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can use `==` in an `if` statement." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "if x == 5:\n", " print('yes, x is 5')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But if you use `=` in an `if` statement, you get an error." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "# If you remove the # from the if statement and run it, you'll get\n", "# SyntaxError: invalid syntax\n", "\n", "#if x = 5:\n", "# print('yes, x is 5')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Exercise:** Add an `else` clause to the `if` statement above, and print an appropriate message.\n", "\n", "Replace the `==` operator with one or two of the other comparison operators, and confirm they do what you expect." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Metrics" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have a working simulation, we'll use it to evaluate alternative designs and see how good or bad they are. The metric we'll use is the number of customers who arrive and find no bikes available, which might indicate a design problem." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First we'll make a new `State` object that creates and initializes additional state variables to keep track of the metrics." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "bikeshare = State(olin=10, wellesley=2, \n", " olin_empty=0, wellesley_empty=0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next we need versions of `bike_to_wellesley` and `bike_to_olin` that update the metrics." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "def bike_to_wellesley(state):\n", " \"\"\"Move one bike from Olin to Wellesley.\n", " \n", " state: bikeshare State object\n", " \"\"\"\n", " if state.olin == 0:\n", " state.olin_empty += 1\n", " return\n", " state.olin -= 1\n", " state.wellesley += 1\n", " \n", "def bike_to_olin(state):\n", " \"\"\"Move one bike from Wellesley to Olin.\n", " \n", " state: bikeshare State object\n", " \"\"\"\n", " if state.wellesley == 0:\n", " state.wellesley_empty += 1\n", " return\n", " state.wellesley -= 1\n", " state.olin += 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now when we run a simulation, it keeps track of unhappy customers." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "run_simulation(bikeshare, 0.4, 0.2, 60)\n", "decorate_bikeshare()\n", "savefig('figs/chap02-fig01.pdf')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After the simulation, we can print the number of unhappy customers at each location." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "bikeshare.olin_empty" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "bikeshare.wellesley_empty" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercises\n", "\n", "**Exercise:** As another metric, we might be interested in the time until the first customer arrives and doesn't find a bike. To make that work, we have to add a \"clock\" to keep track of how many time steps have elapsed:\n", "\n", "1. Create a new `State` object with an additional state variable, `clock`, initialized to 0. \n", "\n", "2. Write a modified version of `step` that adds one to the clock each time it is invoked.\n", "\n", "Test your code by running the simulation and check the value of `clock` at the end." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "bikeshare = State(olin=10, wellesley=2, \n", " olin_empty=0, wellesley_empty=0,\n", " clock=0)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "# Solution goes here" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "# Solution goes here" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "# Solution goes here" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Exercise:** Continuing the previous exercise, let's record the time when the first customer arrives and doesn't find a bike.\n", "\n", "1. Create a new `State` object with an additional state variable, `t_first_empty`, initialized to -1 as a special value to indicate that it has not been set. \n", "\n", "2. Write a modified version of `step` that checks whether`olin_empty` and `wellesley_empty` are 0. If not, it should set `t_first_empty` to `clock` (but only if `t_first_empty` has not already been set).\n", "\n", "Test your code by running the simulation and printing the values of `olin_empty`, `wellesley_empty`, and `t_first_empty` at the end." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "# Solution goes here" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "# Solution goes here" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "# Solution goes here" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "# Solution goes here" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" } }, "nbformat": 4, "nbformat_minor": 2 }