diff --git a/code/Untitled.ipynb b/code/Untitled.ipynb new file mode 100644 index 00000000..12993a94 --- /dev/null +++ b/code/Untitled.ipynb @@ -0,0 +1,89 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "ename": "ImportError", + "evalue": "BeautifulSoup4 (bs4) not found, please install it", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mImportError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mmatplotlib\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpyplot\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0mplt\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 7\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 8\u001b[1;33m \u001b[0mtable1\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mread_html\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'USASAC2015-Report#27-2014-Activities 8.html'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 9\u001b[0m \u001b[0mproduct_tables\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mread_html\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'USASAC2015-Report#27-2014-Activities 10.html'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 10\u001b[0m \u001b[0mtable2\u001b[0m\u001b[1;33m=\u001b[0m \u001b[0mread_html\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'USASAC2015-Report#27-2014-Activities 97-11.html'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mC:\\ProgramData\\Miniconda3\\lib\\site-packages\\pandas\\io\\html.py\u001b[0m in \u001b[0;36mread_html\u001b[1;34m(io, match, flavor, header, index_col, skiprows, attrs, parse_dates, tupleize_cols, thousands, encoding, decimal, converters, na_values, keep_default_na)\u001b[0m\n\u001b[0;32m 904\u001b[0m \u001b[0mthousands\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mthousands\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mattrs\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mattrs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mencoding\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mencoding\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 905\u001b[0m \u001b[0mdecimal\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mdecimal\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mconverters\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mconverters\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mna_values\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mna_values\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 906\u001b[1;33m keep_default_na=keep_default_na)\n\u001b[0m", + "\u001b[1;32mC:\\ProgramData\\Miniconda3\\lib\\site-packages\\pandas\\io\\html.py\u001b[0m in \u001b[0;36m_parse\u001b[1;34m(flavor, io, match, attrs, encoding, **kwargs)\u001b[0m\n\u001b[0;32m 731\u001b[0m \u001b[0mretained\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 732\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mflav\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mflavor\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 733\u001b[1;33m \u001b[0mparser\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0m_parser_dispatch\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mflav\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 734\u001b[0m \u001b[0mp\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mparser\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mio\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcompiled_match\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mattrs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mencoding\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 735\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mC:\\ProgramData\\Miniconda3\\lib\\site-packages\\pandas\\io\\html.py\u001b[0m in \u001b[0;36m_parser_dispatch\u001b[1;34m(flavor)\u001b[0m\n\u001b[0;32m 679\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0m_HAS_BS4\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 680\u001b[0m raise ImportError(\n\u001b[1;32m--> 681\u001b[1;33m \"BeautifulSoup4 (bs4) not found, please install it\")\n\u001b[0m\u001b[0;32m 682\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mbs4\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 683\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mbs4\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__version__\u001b[0m \u001b[1;33m==\u001b[0m \u001b[0mLooseVersion\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'4.2.0'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mImportError\u001b[0m: BeautifulSoup4 (bs4) not found, please install it" + ] + } + ], + "source": [ + "%matplotlib inline\n", + "from modsim import *\n", + "from pandas import read_html\n", + "from numpy import random\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "table1 = read_html('USASAC2015-Report#27-2014-Activities 8.html')\n", + "product_tables = read_html('USASAC2015-Report#27-2014-Activities 10.html')\n", + "table2= read_html('USASAC2015-Report#27-2014-Activities 97-11.html')\n", + "table3 = read_html('USASAC2015-Report#27-2014-Activities 98-12.html')\n", + "return_97 = table2[0]\n", + "return_98 = table3[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'read_html' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mtable1\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mread_html\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'USASAC2015-Report#27-2014-Activities 8.html'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 2\u001b[0m \u001b[0mproduct_tables\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mread_html\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'USASAC2015-Report#27-2014-Activities 10.html'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[0mtable2\u001b[0m\u001b[1;33m=\u001b[0m \u001b[0mread_html\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'USASAC2015-Report#27-2014-Activities 97-11.html'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[0mtable3\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mread_html\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'USASAC2015-Report#27-2014-Activities 98-12.html'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[0mreturn_97\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mtable2\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mNameError\u001b[0m: name 'read_html' is not defined" + ] + } + ], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/code/chap01_fig01.pdf b/code/chap01_fig01.pdf new file mode 100644 index 00000000..891c7ad6 Binary files /dev/null and b/code/chap01_fig01.pdf differ diff --git a/code/chap01mine.ipynb b/code/chap01mine.ipynb new file mode 100644 index 00000000..f143d6e5 --- /dev/null +++ b/code/chap01mine.ipynb @@ -0,0 +1,6952 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Modeling and Simulation in Python\n", + "\n", + "Chapter 1: Modeling\n", + "\n", + "Copyright 2017 Allen Downey\n", + "\n", + "License: [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Jupyter\n", + "\n", + "Welcome to Modeling and Simulation, welcome to Python, and welcome to Jupyter.\n", + "\n", + "This is a Jupyter notebook, which is a development environment where you can write and run Python code. Each notebook is divided into cells. Each cell contains either text (like this cell) or Python code (like the cell below this one).\n", + "\n", + "### Selecting and running cells\n", + "\n", + "To select a cell, click in the left margin next to the cell. You should see a blue frame surrounding the selected cell.\n", + "\n", + "To edit a code cell, click inside the cell. You should see a green frame around the selected cell, and you should see a cursor inside the cell.\n", + "\n", + "To edit a text cell, double-click inside the cell. Again, you should see a green frame around the selected cell, and you should see a cursor inside the cell.\n", + "\n", + "To run a cell, hold down SHIFT and press ENTER. If you run a text cell, it will typeset the text and display the result.\n", + "\n", + "If you run a code cell, it runs the Python code in the cell and displays the result, if any.\n", + "\n", + "To try it out, edit this cell, change some of the text, and then press SHIFT-ENTER to run it." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Adding and removing cells\n", + "\n", + "You can add and remove cells from a notebook using the buttons in the toolbar and the items in the menu, both of which you should see at the top of this notebook.\n", + "\n", + "You might want to try the following exercises:\n", + "\n", + "1. From the Insert menu select \"Insert cell below\" to add a cell below this one. By default, you get a code cell, and you can see in the pulldown menu that says \"Code\".\n", + "\n", + "2. In the new cell, add a print statement like `print('Hello')`, and run it.\n", + "\n", + "3. Add another cell, select the new cell, and then click on the pulldown menu that says \"Code\" and select \"Markdown\". This makes the new cell a text cell.\n", + "\n", + "4. In the new cell, type some text, and then run it.\n", + "\n", + "5. Use the arrow buttons in the toolbar to move cells up and down.\n", + "\n", + "6. Use the cut, copy, and paste buttons to delete, add, and move cells.\n", + "\n", + "7. As you make changes, Jupyter saves your notebook automatically, but if you want to make sure, you can press the save button, which looks like a floppy disk from the 1990s.\n", + "\n", + "8. Finally, when you are done with a notebook, selection \"Close and Halt\" from the File menu." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using the notebooks\n", + "\n", + "The notebooks for each chapter contain the code from the chapter along with addition examples, explanatory text, and exercises. I recommend you read the chapter first to understand the concepts and vocabulary, then run the notebook to review what you learned and see it in action, and then attempt the exercises.\n", + "\n", + "The notebooks contain some explanatory text, but it is probably not enough to make sense if you have not read the book. If you are working through a notebook and you get stuck, you might want to re-read (or read!) the corresponding section of the book.\n", + "\n", + "If you try to work through the notebooks without reading the book, you're gonna have a bad time. If you have previous programming experience, you might get through the first few notebooks, but sooner or later, you will get to the end of your leash, and you won't like it." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Importing modsim\n", + "\n", + "The following cell imports `modsim`, which is a collection of functions we will use throughout the book. Whenever you start the notebook, you will have to run the following cell. It does two things:\n", + "\n", + "1. It uses a Jupyter \"magic command\" to specify whether figures should appear in the notebook, or pop up in a new window.\n", + "\n", + "2. It imports everything defined in `modsim`.\n", + "\n", + "Select the following cell and press SHIFT-ENTER to run it." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "If this cell runs successfully, it produces no output other than this message.\n" + ] + } + ], + "source": [ + "# If you want the figures to appear in the notebook, \n", + "# and you want to interact with them, use\n", + "# %matplotlib notebook\n", + "\n", + "# If you want the figures to appear in the notebook, \n", + "# and you don't want to interact with them, use\n", + "# %matplotlib inline\n", + "\n", + "# If you want the figures to appear in separate windows, use\n", + "# %matplotlib qt5\n", + "\n", + "# To switch from one to another, you have to select Kernel->Restart\n", + "\n", + "%matplotlib notebook\n", + "\n", + "from modsim import *\n", + "\n", + "print('If this cell runs successfully, it produces no output other than this message.')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The penny myth\n", + "\n", + "The following cells contain code from the beginning of Chapter 1.\n", + "\n", + "`modsim` defines `UNITS`, which contains variables representing pretty much every unit you've ever heard of. The following to lines create new variables named `meter` and `second`." + ] + }, + { + "cell_type": "code", + "execution_count": 136, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "meter = UNITS.meter\n", + "second = UNITS.second" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To find out what units are defined, type `UNITS.` in the next cell and then press TAB. You should see a pop-up menu with a list of units." + ] + }, + { + "cell_type": "code", + "execution_count": 137, + "metadata": {}, + "outputs": [ + { + "ename": "SyntaxError", + "evalue": "invalid syntax (, line 1)", + "output_type": "error", + "traceback": [ + "\u001b[1;36m File \u001b[1;32m\"\"\u001b[1;36m, line \u001b[1;32m1\u001b[0m\n\u001b[1;33m UNITS.\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m invalid syntax\n" + ] + } + ], + "source": [ + "UNITS." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create a variable named `a` and display its value:" + ] + }, + { + "cell_type": "code", + "execution_count": 138, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "9.8 meter/second2" + ], + "text/latex": [ + "$9.8 \\frac{meter}{second^{2}}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 138, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a = 9.8 * meter / second**2\n", + "a" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Create `t` and display its value:" + ] + }, + { + "cell_type": "code", + "execution_count": 139, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "4 second" + ], + "text/latex": [ + "$4 second$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 139, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "t = 4 * second\n", + "t" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "If you create a variable and don't display the value, you don't get any output:" + ] + }, + { + "cell_type": "code", + "execution_count": 140, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "h = a * t**2" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Add a second line to the previous cell to display the value of `h`.\n", + "\n", + "Now let's solve the falling penny problem. The following lines set `h` to the height of the Empire State Building and compute the time it would take a penny to fall, assuming constant acceleration." + ] + }, + { + "cell_type": "code", + "execution_count": 141, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "8.817885349720552 second" + ], + "text/latex": [ + "$8.817885349720552 second$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 141, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "h = 381 * meter\n", + "t = sqrt(2 * h / a)\n", + "t" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Given `t`, we can compute the velocity of the penny when it lands." + ] + }, + { + "cell_type": "code", + "execution_count": 142, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "86.41527642726142 meter/second" + ], + "text/latex": [ + "$86.41527642726142 \\frac{meter}{second}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 142, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v = a * t\n", + "v" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "We can convert from one set of units to another like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 143, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "mile = UNITS.mile\n", + "hour= UNITS.hour" + ] + }, + { + "cell_type": "code", + "execution_count": 144, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "193.30546802805438 mile/hour" + ], + "text/latex": [ + "$193.30546802805438 \\frac{mile}{hour}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 144, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v.to(mile/hour)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** In reality, air resistance prevents the penny from reaching this velocity. At about 20 meters per second, the force of air resistance equals the force of gravity and the penny stops accelerating.\n", + "\n", + "As a simplification, let's assume that the acceleration of the penny is `a` until the penny reaches 20 meters per second, and then 0 afterwards. What is the total time for the penny to fall 381 meters?" + ] + }, + { + "cell_type": "code", + "execution_count": 157, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.0408163265306123 second\n", + "20.408163265306126 meter\n", + "360.59183673469386 meter\n", + "18.029591836734692 second\n", + "Total time: 20.070408163265306 second\n" + ] + } + ], + "source": [ + "t = 20*(meter/second)/a\n", + "print(t)\n", + "h = .5*a*t**2\n", + "print(h)\n", + "h_left = (381*meter)- h\n", + "print(h_left)\n", + "t_left = h_left/(20*(meter/second))\n", + "#t_left = sqrt(2 * h_left / a)\n", + "print(t_left)\n", + "total_time = t + t_left\n", + "print('Total time:', total_time)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Modeling a bikeshare system" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We'll start with a `System` object that represents the number of bikes at each station." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "bikeshare = System(olin=10, wellesley=2, babson = 0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you display the value of a `System` object, it lists the system variables and their values (not necessarily in the order you defined them):" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
olin10
wellesley2
\n", + "
" + ], + "text/plain": [ + "olin 10\n", + "wellesley 2\n", + "dtype: int64" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bikeshare" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can access the system variables using dot notation." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "10" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bikeshare.olin" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
olin10
wellesley2
babson0
\n", + "
" + ], + "text/plain": [ + "olin 10\n", + "wellesley 2\n", + "babson 0\n", + "dtype: int64" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bikeshare.wellesley\n", + "bikeshare\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** What happens if you spell the name of a system variable wrong? Edit the previous cell, change the spelling of `wellesley`, and run the cell again.\n", + "\n", + "The error message uses the word \"attribute\", which is another name for what we are calling a system variable. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Add a third attribute called `babson` with initial value 0, and print the state of `bikeshare` again." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plotting\n", + "\n", + "`newfig` creates a new figure, which should appear either in the notebook or in a new window, depending on which magic command you ran in the first code cell.\n", + "\n", + "`plot` adds a data point to the figure; in this example, you should see a red square and a blue circle representing the number of bikes at each station." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "bikeshare = System(olin=10, wellesley=2)\n", + "newfig()\n", + "plot_state()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since we have two similar functions, we can create a new function, `move_bike` that takes a parameter `n`, which indicates how many bikes are moving, and in which direction." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def move_bike(n):\n", + " bikeshare.olin -= n\n", + " bikeshare.wellesley += n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can use `move_bike` to write simpler versions of the other functions." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def bike_to_wellesley():\n", + " move_bike(1)\n", + " \n", + "def bike_to_olin():\n", + " move_bike(-1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we define these functions, we replace the old definitions with the new ones.\n", + "\n", + "Now we can test them and update the figure." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
olin9
wellesley3
\n", + "
" + ], + "text/plain": [ + "olin 9\n", + "wellesley 3\n", + "dtype: int64" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bike_to_wellesley()\n", + "plot_state()\n", + "bikeshare" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again, each time you run `plot_state` you should see changes in the figure." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
olin10
wellesley2
\n", + "
" + ], + "text/plain": [ + "olin 10\n", + "wellesley 2\n", + "dtype: int64" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bike_to_olin()\n", + "plot_state()\n", + "bikeshare" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "At this point, `move_bike` is complicated enough that we should add some documentation. The text in triple-quotation marks is in English, not Python. It doesn't do anything when the program runs, but it helps people understand what this function does and how to use it." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def move_bike(n):\n", + " \"\"\"Move bikes.\n", + " \n", + " n: number of bikes: positive moves from Olin to Wellesley;\n", + " negative moves from Wellesley to Olin\n", + " \"\"\"\n", + " bikeshare.olin -= n\n", + " bikeshare.wellesley += n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Whenever you make a figure, you should put labels on the axes to explain what they mean and what units they are measured in. Here's how:" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "label_axes(title='Olin-Wellesley Bikeshare',\n", + " xlabel='Time step (min)', \n", + " ylabel='Number of bikes')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again, you might have to scroll up to see the effect.\n", + "\n", + "And you can save figures as files; the suffix of the filename indicates the format you want. This example saves the current figure in a PDF file." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving figure to file chap01_fig01.pdf\n" + ] + } + ], + "source": [ + "savefig('chap01_fig01.pdf')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** The following function definitions start with print statements so they display messages when they run. Run each of these functions (with appropriate arguments) and confirm that they do what you expect.\n", + "\n", + "Adding print statements like this to functions is a useful debugging technique. Keep it in mind!" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def move_bike_debug(n):\n", + " print('Running move_bike_debug with argument', n)\n", + " bikeshare.olin -= n\n", + " bikeshare.wellesley += n\n", + " \n", + "def bike_to_wellesley_debug():\n", + " print('Running bike_to_wellesley_debug')\n", + " move_bike_debug(1)\n", + " \n", + "def bike_to_olin_debug():\n", + " print('Running bike_to_olin_debug')\n", + " move_bike_debug(-1)" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running move_bike_debug with argument 1\n" + ] + } + ], + "source": [ + "move_bike_debug(1)" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running bike_to_wellesley_debug\n", + "Running move_bike_debug with argument 1\n" + ] + } + ], + "source": [ + "bike_to_wellesley_debug()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running bike_to_olin_debug\n", + "Running move_bike_debug with argument -1\n" + ] + } + ], + "source": [ + "bike_to_olin_debug()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conditionals" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The function `flip` takes a probability and returns either `True` or `False`, which are special values defined by Python.\n", + "\n", + "In the following example, the probability is 0.7 or 70%. If you run this cell several times, you should get `True` about 70% of the time and `False` about 30%." + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 68, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "flip(0.7)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Modify the argument in the previous cell and see what effect it has.\n", + "\n", + "In the following example, we use `flip` as part of an if statement. If the result from `flip` is `True`, we print `heads`; otherwise we do nothing." + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "if flip(0.7):\n", + " print('heads')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With an else clause, we can print heads or tails depending on whether `flip` returns `True` or `False`." + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "heads\n" + ] + } + ], + "source": [ + "if flip(0.7):\n", + " print('heads')\n", + "else:\n", + " print('tails')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's get back to the bikeshare system. Again let's start with a new `System` object and a new plot." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "bikeshare = System(olin=10, wellesley=2)\n", + "newfig()\n", + "plot_state()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can make `step` more general by adding parameters. Because these parameters have default values, they are optional." + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def step(p1=0.5, p2=0.5):\n", + " print('p1 ->', p1)\n", + " print('p2 ->', p2)\n", + " if flip(p1):\n", + " bike_to_wellesley()\n", + " \n", + " if flip(p2):\n", + " bike_to_olin()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "I added print statements, so each time we run `step` we can see the arguments.\n", + "\n", + "If you provide no arguments, you get the default values:" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "p1 -> 0.5\n", + "p2 -> 0.5\n" + ] + } + ], + "source": [ + "step()\n", + "plot_state()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you provide one argument, it overrides the first parameter." + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "p1 -> 0.4\n", + "p2 -> 0.5\n" + ] + } + ], + "source": [ + "step(0.4)\n", + "plot_state()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you provide two arguments, they override both." + ] + }, + { + "cell_type": "code", + "execution_count": 110, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "p1 -> 0.4\n", + "p2 -> 0.2\n" + ] + } + ], + "source": [ + "step(0.4, 0.2)\n", + "plot_state()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can specify the names of the parameters you want to override." + ] + }, + { + "cell_type": "code", + "execution_count": 111, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "p1 -> 0.4\n", + "p2 -> 0.2\n" + ] + } + ], + "source": [ + "step(p1=0.4, p2=0.2)\n", + "plot_state()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Which means you can override the second parameter and use the default for the first." + ] + }, + { + "cell_type": "code", + "execution_count": 112, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "p1 -> 0.5\n", + "p2 -> 0.2\n" + ] + } + ], + "source": [ + "step(p2=0.2)\n", + "plot_state()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can combine both forms, but it is not very common:" + ] + }, + { + "cell_type": "code", + "execution_count": 113, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "p1 -> 0.4\n", + "p2 -> 0.2\n" + ] + } + ], + "source": [ + "step(0.4, p2=0.2)\n", + "plot_state()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One reason it's not common is that it's error prone. The following example causes an error." + ] + }, + { + "cell_type": "code", + "execution_count": 114, + "metadata": {}, + "outputs": [ + { + "ename": "SyntaxError", + "evalue": "positional argument follows keyword argument (, line 4)", + "output_type": "error", + "traceback": [ + "\u001b[1;36m File \u001b[1;32m\"\"\u001b[1;36m, line \u001b[1;32m4\u001b[0m\n\u001b[1;33m step(p1=0.4, 0.2)\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m positional argument follows keyword argument\n" + ] + } + ], + "source": [ + "# If you remove the # at the beginning of the next line and run it, you get\n", + "# SyntaxError: positional argument follows keyword argument\n", + "\n", + "step(p1=0.4, 0.2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From the error message, you might infer that arguments like `step(0.4, 0.2)` are called \"positional\" and arguments like `step(p1=0.4, p2=0.2)` are called \"keyword arguments\"." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Write a version of `decorate` that takes an optional parameter named `loc` with default value `'best'`. It should pass the value of `loc` along as an argument to `legend.` Test your function with different values of `loc`. [You can see the list of legal values here](https://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.legend)." + ] + }, + { + "cell_type": "code", + "execution_count": 115, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def decorate(location): \n", + " legend(loc= location )\n", + " label_axes(title='Olin-Wellesley Bikeshare',\n", + " xlabel='Time step (min)', \n", + " ylabel='Number of bikes')\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n", + "2\n", + "3\n" + ] + } + ], + "source": [ + "decorate('center left')\n", + "\n", + "for i in range(4):\n", + " print(i)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## For loop" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Before we go on, I'll redefine `step` without the print statements." + ] + }, + { + "cell_type": "code", + "execution_count": 117, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def step(p1=0.5, p2=0.5):\n", + " if flip(p1):\n", + " bike_to_wellesley()\n", + " \n", + " if flip(p2):\n", + " bike_to_olin()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And let's start again with a new `System` object and a new figure." + ] + }, + { + "cell_type": "code", + "execution_count": 118, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "ename": "TypeError", + "evalue": "decorate() missing 1 required positional argument: 'location'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0mnewfig\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[0mplot_state\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[0mdecorate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 4\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mrun_steps\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mnum_steps\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mp1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mp2\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mnum_steps\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mTypeError\u001b[0m: decorate() missing 1 required positional argument: 'location'" + ] + } + ], + "source": [ + "newfig()\n", + "plot_state()\n", + "decorate()\n", + "def run_steps(num_steps, p1, p2):\n", + " for i in range(num_steps):\n", + " step(p1, p2)\n", + " plot_state()" + ] + }, + { + "cell_type": "code", + "execution_count": 134, + "metadata": {}, + "outputs": [], + "source": [ + "run_steps(55,.5,.5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/code/chap02mine.ipynb b/code/chap02mine.ipynb new file mode 100644 index 00000000..81d36cbc --- /dev/null +++ b/code/chap02mine.ipynb @@ -0,0 +1,8682 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Modeling and Simulation in Python\n", + "\n", + "Chapter 2: Simulation\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": "markdown", + "metadata": {}, + "source": [ + "We'll start with the same code we saw last time: the magic command that tells Jupyter where to put the figures, and the import statement that gets the functions defined in the `modsim` module." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# If you want the figures to appear in the notebook, \n", + "# and you want to interact with them, use\n", + "# %matplotlib notebook\n", + "\n", + "# If you want the figures to appear in the notebook, \n", + "# and you don't want to interact with them, use\n", + "# %matplotlib inline\n", + "\n", + "# If you want the figures to appear in separate windows, use\n", + "# %matplotlib qt5\n", + "\n", + "%matplotlib notebook\n", + "\n", + "from modsim import *" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## More than one System 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 `system` to the functions so they work with whatever `System` object we give them, instead of always using `bikeshare`. That will be useful soon when we have more than one `System` object." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def run_steps(system, num_steps=1, p1=0.5, p2=0.5):\n", + " \"\"\"Simulate the given number of time steps.\n", + " \n", + " system: bikeshare System object\n", + " num_steps: number of time steps\n", + " p1: probability of an Olin->Wellesley customer arrival\n", + " p2: probability of a Wellesley->Olin customer arrival\n", + " \"\"\"\n", + " for i in range(num_steps):\n", + " step(system, p1, p2)\n", + " plot_system(system)\n", + " \n", + "def step(system, p1=0.5, p2=0.5):\n", + " \"\"\"Simulate one minute of time.\n", + " \n", + " system: bikeshare System 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(system)\n", + " \n", + " if flip(p2):\n", + " bike_to_olin(system)\n", + " \n", + "def bike_to_wellesley(system):\n", + " \"\"\"Move one bike from Olin to Wellesley.\n", + " \n", + " system: bikeshare System object\n", + " \"\"\"\n", + " move_bike(system, 1)\n", + " \n", + "def bike_to_olin(system):\n", + " \"\"\"Move one bike from Wellesley to Olin.\n", + " \n", + " system: bikeshare System object\n", + " \"\"\"\n", + " move_bike(system, -1)\n", + " \n", + "def move_bike(system, n):\n", + " \"\"\"Move a bike.\n", + " \n", + " system: bikeshare System object\n", + " n: +1 to move from Olin to Wellesley or\n", + " -1 to move from Wellesley to Olin\n", + " \"\"\"\n", + " system.olin -= n\n", + " system.wellesley += n\n", + " \n", + "def plot_system(system):\n", + " \"\"\"Plot the current system of the bikeshare system.\n", + " \n", + " system: bikeshare System object\n", + " \"\"\"\n", + " plot(system.olin, 'rs-', label='Olin')\n", + " plot(system.wellesley, 'bo-', label='Wellesley')\n", + " \n", + "def decorate():\n", + " \"\"\"Add a legend and label the axes.\n", + " \"\"\"\n", + " legend(loc='best')\n", + " label_axes(title='Olin-Wellesley Bikeshare',\n", + " xlabel='Time step (min)', \n", + " ylabel='Number of bikes')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can create more than one `System` object:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
olin10
wellesley2
\n", + "
" + ], + "text/plain": [ + "olin 10\n", + "wellesley 2\n", + "dtype: int64" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bikeshare1 = System(olin=10, wellesley=2)\n", + "bikeshare1" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
olin10
wellesley2
\n", + "
" + ], + "text/plain": [ + "olin 10\n", + "wellesley 2\n", + "dtype: int64" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bikeshare2 = System(olin=10, wellesley=2)\n", + "bikeshare2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And whenever we call a function, we indicate which `System` object to work with:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "bike_to_olin(bikeshare1)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "bike_to_wellesley(bikeshare2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And you can confirm that the different systems are getting updated independently:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
olin11
wellesley1
\n", + "
" + ], + "text/plain": [ + "olin 11\n", + "wellesley 1\n", + "dtype: int64" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bikeshare1" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
olin9
wellesley3
\n", + "
" + ], + "text/plain": [ + "olin 9\n", + "wellesley 3\n", + "dtype: int64" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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 quite often." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "IOPub data rate exceeded.\n", + "The notebook server will temporarily stop sending output\n", + "to the client in order to avoid crashing it.\n", + "To change this limit, set the config variable\n", + "`--NotebookApp.iopub_data_rate_limit`.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + ":(\n" + ] + } + ], + "source": [ + "bikeshare = System(olin=10, wellesley=2)\n", + "newfig()\n", + "plot_system(bikeshare)\n", + "decorate()\n", + "run_steps(bikeshare, 60, 0.4, 0.2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The variables `olin` and `wellesley` are created inside `move_bike`, so they are local. When the function ends, they go away.\n", + "\n", + "If you try to access a local variable from outside its function, you get an error:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# If you remove the # from the last line in this cell and run it, you'll get\n", + "# NameError: name 'olin' is not defined\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Add print statements in `move_bike` so it prints a message each time a customer arrives and doesn't find a bike. Run the simulation again to confirm that it works as you expect. Then you might want to remove the print statements before you go on." + ] + }, + { + "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": 25, + "metadata": { + "collapsed": true + }, + "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": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x == 5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can use `==` in an `if` statement." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "yes, x is 5\n" + ] + } + ], + "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": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "yes, x is 5\n" + ] + } + ], + "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')\n", + "else:\n", + " print(\"x ain't 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 `System` object that creates and initializes the system variables that will keep track of the metrics." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "bikeshare = System(olin=10, wellesley=2, \n", + " olin_empty=0, wellesley_empty=0,clock =0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we need a version of `move_bike` that updates the metrics." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def move_bike(system, n):\n", + " olin_temp = system.olin - n\n", + " if olin_temp < 0:\n", + " system.olin_empty += 1\n", + " return\n", + " \n", + " wellesley_temp = system.wellesley + n\n", + " if wellesley_temp < 0:\n", + " system.wellesley_empty += 1\n", + " return\n", + " \n", + " system.olin = olin_temp\n", + " system.wellesley = wellesley_temp" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now when we run a simulation, it keeps track of unhappy customers." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Clock: 1\n", + "Clock: 2\n", + "Clock: 3\n", + "Clock: 4\n", + "Clock: 5\n", + "Clock: 6\n", + "Clock: 7\n", + "Clock: 8\n", + "Clock: 9\n", + "Clock: 10\n", + "Clock: 11\n", + "Clock: 12\n", + "Clock: 13\n", + "Clock: 14\n", + "Clock: 15\n", + "Clock: 16\n", + "Clock: 17\n", + "Clock: 18\n", + "Clock: 19\n", + "Clock: 20\n", + "Clock: 21\n", + "Clock: 22\n", + "Clock: 23\n", + "Clock: 24\n", + "Clock: 25\n", + "Clock: 26\n", + "Clock: 27\n", + "Clock: 28\n", + "Clock: 29\n", + "Clock: 30\n", + "Clock: 31\n", + "Clock: 32\n", + "Clock: 33\n", + "Clock: 34\n", + "Clock: 35\n", + "Clock: 36\n", + "Clock: 37\n", + "Clock: 38\n", + "Clock: 39\n", + "Clock: 40\n", + "Clock: 41\n", + "Clock: 42\n", + "Clock: 43\n", + "Clock: 44\n", + "Clock: 45\n", + "Clock: 46\n", + "Clock: 47\n", + "Clock: 48\n", + "Clock: 49\n", + "Clock: 50\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "IOPub data rate exceeded.\n", + "The notebook server will temporarily stop sending output\n", + "to the client in order to avoid crashing it.\n", + "To change this limit, set the config variable\n", + "`--NotebookApp.iopub_data_rate_limit`.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Clock: 59\n", + "Clock: 60\n" + ] + } + ], + "source": [ + "newfig()\n", + "plot_system(bikeshare)\n", + "decorate()\n", + "run_steps(bikeshare, 60, 0.4, 0.2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After the simulation, check the final value of `clock`." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "61" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bikeshare.clock\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Now suppose we'd like to know how long it takes to run out of bikes at either location. Modify `move_bike` so the first time a student arrives at Olin and doesn't find a bike, it records the value of `clock` in a system variable.\n", + "\n", + "Hint: create a system variable named `t_first_empty` and initialize it to `None`, which is a special value (like `True` and `False`) that can be used to indicate a \"special case\".\n", + "\n", + "Test your code by running a simulation for 60 minutes and checking the metrics." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Yup, example is None.\n" + ] + } + ], + "source": [ + "example = None\n", + "\n", + "if example == None:\n", + " print('Yup, example is None.')" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "bikeshare = System(olin=10, wellesley=2, \n", + " olin_empty=0, wellesley_empty=0,clock =0, t_first_empty = 0, triggered = False)" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def move_bike(system, n):\n", + " olin_temp = system.olin - n\n", + " if olin_temp < 0:\n", + " \n", + " if system.triggered == False:\n", + " print(\"EMPTY AT OLIN\")\n", + " system.t_first_empty = system.clock\n", + " print(\"Time (min): \", system.t_first_empty)\n", + " system.triggered = True\n", + " return\n", + "\n", + " wellesley_temp = system.wellesley + n\n", + " if wellesley_temp < 0:\n", + " \n", + " if system.triggered == False:\n", + " print(\"EMPTY AT WELLESLEY \")\n", + " system.t_first_empty = system.clock\n", + " print(\"Time (min): \", system.t_first_empty)\n", + " system.triggered = True\n", + " return\n", + "\n", + " # update the system\n", + " system.olin = olin_temp\n", + " system.wellesley = wellesley_temp" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "newfig()\n", + "for p1 in p1_array:\n", + " system = run_simulation(p1=p1)\n", + " plot(p1, system.olin_empty, 'rs', label='Olin')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As always, we should decorate the figure. This version of `decorate` takes `xlabel` as a parameter, for reasons you will see soon." + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def decorate(xlabel):\n", + " legend(loc='best')\n", + " label_axes(title='Olin-Wellesley Bikeshare',\n", + " xlabel=xlabel, \n", + " ylabel='Number of unhappy customers')" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "decorate(xlabel='Arrival rate')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Wrap this code in a function named `parameter_sweep` that takes an array called `p1_array` as a parameter. It should create a new figure, run a simulation for each value of `p1` in `p1_array`, and plot the results.\n", + "\n", + "Once you have the function working, modify it so it also plots the number of unhappy customers at Wellesley. Looking at the plot, can you estimate a range of values for `p1` that minimizes the total number of unhappy customers?" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": {}, + "outputs": [], + "source": [ + "p1_array = linspace(0,1,14)\n", + "def parameter_sweep(p1_array):\n", + " for p1 in p1_array:\n", + " system = run_simulation(p1)\n", + "\n", + " \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "13.0\n", + "11.0\n", + "9.8\n", + "8.7\n", + "9.0\n", + "11.1\n", + "10.1\n", + "7.3\n", + "4.7\n", + "4.4\n" + ] + } + ], + "source": [ + "newfig()\n", + "\n", + "for i in range(10):\n", + " average = run_simulations(num_runs = 10, olin = i)\n", + " plot(i, average, 'gs', \"Average\")\n", + " print(average)" + ] + }, + { + "cell_type": "code", + "execution_count": 136, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/code/chap02soln.ipynb b/code/chap02soln.ipynb index 6463279a..bd945845 100644 --- a/code/chap02soln.ipynb +++ b/code/chap02soln.ipynb @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "metadata": { "collapsed": true }, @@ -59,7 +59,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "metadata": { "collapsed": true }, @@ -138,7 +138,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -184,7 +184,7 @@ "dtype: int64" ] }, - "execution_count": 7, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -196,7 +196,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -242,7 +242,7 @@ "dtype: int64" ] }, - "execution_count": 8, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -261,7 +261,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 7, "metadata": { "collapsed": true }, @@ -272,7 +272,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "metadata": { "collapsed": true }, @@ -290,7 +290,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -336,7 +336,7 @@ "dtype: int64" ] }, - "execution_count": 11, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -347,7 +347,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -393,7 +393,7 @@ "dtype: int64" ] }, - "execution_count": 12, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -420,7 +420,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -982,7 +982,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -1203,7 +1203,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1243,7 +1243,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 12, "metadata": { "collapsed": true }, @@ -1273,7 +1273,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -1835,7 +1835,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -2056,7 +2056,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2064,17 +2064,6 @@ }, "metadata": {}, "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "IOPub data rate exceeded.\n", - "The notebook server will temporarily stop sending output\n", - "to the client in order to avoid crashing it.\n", - "To change this limit, set the config variable\n", - "`--NotebookApp.iopub_data_rate_limit`.\n" - ] } ], "source": [ @@ -2096,7 +2085,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 19, "metadata": { "collapsed": true }, @@ -2135,7 +2124,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 20, "metadata": { "collapsed": true }, @@ -2153,7 +2142,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 21, "metadata": {}, "outputs": [ { @@ -2162,7 +2151,7 @@ "True" ] }, - "execution_count": 18, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -2180,7 +2169,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -2205,7 +2194,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 23, "metadata": { "collapsed": true }, @@ -2250,7 +2239,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 24, "metadata": { "collapsed": true }, @@ -2269,7 +2258,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 25, "metadata": { "collapsed": true }, @@ -2299,7 +2288,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 26, "metadata": {}, "outputs": [ { @@ -2861,7 +2850,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -3082,7 +3071,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3119,16 +3108,16 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "0" + "5" ] }, - "execution_count": 24, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -3139,7 +3128,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 30, "metadata": {}, "outputs": [ { @@ -3148,7 +3137,7 @@ "0" ] }, - "execution_count": 25, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -3172,7 +3161,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 31, "metadata": { "collapsed": true }, @@ -3196,7 +3185,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 32, "metadata": { "collapsed": true }, @@ -3211,7 +3200,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 33, "metadata": { "collapsed": true }, @@ -3238,7 +3227,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 34, "metadata": {}, "outputs": [ { @@ -3800,7 +3789,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -4021,7 +4010,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4049,7 +4038,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 35, "metadata": { "collapsed": true }, @@ -4071,7 +4060,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 36, "metadata": {}, "outputs": [ { @@ -4137,7 +4126,7 @@ "dtype: int64" ] }, - "execution_count": 42, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -4154,7 +4143,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 37, "metadata": { "collapsed": true }, @@ -4181,7 +4170,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 38, "metadata": {}, "outputs": [ { @@ -4743,7 +4732,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -4964,7 +4953,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -5003,8 +4992,10 @@ }, { "cell_type": "code", - "execution_count": 48, - "metadata": {}, + "execution_count": 40, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# print(bikeshare.t_first_empty)" @@ -5019,7 +5010,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 41, "metadata": { "collapsed": true }, @@ -5063,7 +5054,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 42, "metadata": { "collapsed": true }, @@ -5082,7 +5073,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 43, "metadata": {}, "outputs": [ { @@ -5091,7 +5082,7 @@ "8" ] }, - "execution_count": 51, + "execution_count": 43, "metadata": {}, "output_type": "execute_result" } @@ -5110,7 +5101,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 44, "metadata": {}, "outputs": [ { @@ -5119,7 +5110,7 @@ "10" ] }, - "execution_count": 52, + "execution_count": 44, "metadata": {}, "output_type": "execute_result" } @@ -5141,7 +5132,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 45, "metadata": {}, "outputs": [ { @@ -5150,7 +5141,7 @@ "10" ] }, - "execution_count": 53, + "execution_count": 45, "metadata": {}, "output_type": "execute_result" } @@ -5171,7 +5162,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 46, "metadata": {}, "outputs": [ { @@ -5200,7 +5191,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 47, "metadata": { "collapsed": true }, @@ -5215,7 +5206,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 48, "metadata": {}, "outputs": [ { @@ -5261,7 +5252,7 @@ "dtype: int64" ] }, - "execution_count": 58, + "execution_count": 48, "metadata": {}, "output_type": "execute_result" } @@ -5293,7 +5284,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 49, "metadata": { "collapsed": true }, @@ -5325,7 +5316,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 50, "metadata": { "collapsed": true }, @@ -5345,16 +5336,16 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 51, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "0" + "10" ] }, - "execution_count": 62, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -5372,7 +5363,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 52, "metadata": { "collapsed": true }, @@ -5394,7 +5385,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 53, "metadata": { "collapsed": true }, @@ -5405,14 +5396,14 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 54, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "4 0\n" + "6 0\n" ] } ], @@ -5429,7 +5420,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 55, "metadata": { "collapsed": true }, @@ -5451,7 +5442,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 56, "metadata": {}, "outputs": [ { @@ -5460,7 +5451,7 @@ "0" ] }, - "execution_count": 68, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } @@ -5479,16 +5470,16 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 57, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "11" + "10" ] }, - "execution_count": 69, + "execution_count": 57, "metadata": {}, "output_type": "execute_result" } @@ -5509,7 +5500,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 58, "metadata": { "collapsed": true }, @@ -5526,14 +5517,14 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 59, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "0 0\n" + "0 1\n" ] } ], @@ -5562,7 +5553,7 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 60, "metadata": {}, "outputs": [ { @@ -5571,7 +5562,7 @@ "array([ 0. , 0.25, 0.5 , 0.75, 1. ])" ] }, - "execution_count": 72, + "execution_count": 60, "metadata": {}, "output_type": "execute_result" } @@ -5590,7 +5581,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 61, "metadata": {}, "outputs": [ { @@ -5628,7 +5619,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 62, "metadata": {}, "outputs": [ { @@ -5637,7 +5628,7 @@ "array([ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])" ] }, - "execution_count": 74, + "execution_count": 62, "metadata": {}, "output_type": "execute_result" } @@ -5657,7 +5648,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 63, "metadata": {}, "outputs": [ { @@ -5701,7 +5692,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 64, "metadata": {}, "outputs": [ { @@ -5710,7 +5701,7 @@ "array([ 1., 3., 5., 7., 9., 11.])" ] }, - "execution_count": 79, + "execution_count": 64, "metadata": {}, "output_type": "execute_result" } @@ -5739,7 +5730,7 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": 65, "metadata": {}, "outputs": [ { @@ -5748,7 +5739,7 @@ "array([ 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])" ] }, - "execution_count": 80, + "execution_count": 65, "metadata": {}, "output_type": "execute_result" } @@ -5760,7 +5751,7 @@ }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 67, "metadata": {}, "outputs": [ { @@ -5771,13 +5762,13 @@ "0.1 0\n", "0.2 0\n", "0.3 0\n", - "0.4 9\n", - "0.5 15\n", - "0.6 13\n", - "0.7 17\n", - "0.8 26\n", - "0.9 33\n", - "1.0 35\n" + "0.4 6\n", + "0.5 7\n", + "0.6 10\n", + "0.7 23\n", + "0.8 30\n", + "0.9 35\n", + "1.0 34\n" ] } ], @@ -5797,7 +5788,7 @@ }, { "cell_type": "code", - "execution_count": 82, + "execution_count": 68, "metadata": {}, "outputs": [ { @@ -6359,7 +6350,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -6580,7 +6571,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -6606,7 +6597,7 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 69, "metadata": { "collapsed": true }, @@ -6620,8 +6611,10 @@ }, { "cell_type": "code", - "execution_count": 84, - "metadata": {}, + "execution_count": 70, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "decorate_bikeshare(xlabel='Arrival rate at Olin (p1 in customers/min)')" @@ -6660,7 +6653,7 @@ }, { "cell_type": "code", - "execution_count": 86, + "execution_count": 72, "metadata": {}, "outputs": [ { @@ -7222,7 +7215,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -7443,7 +7436,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -7475,7 +7468,7 @@ }, { "cell_type": "code", - "execution_count": 87, + "execution_count": 73, "metadata": { "collapsed": true }, @@ -7492,7 +7485,7 @@ }, { "cell_type": "code", - "execution_count": 88, + "execution_count": 74, "metadata": {}, "outputs": [ { @@ -8054,7 +8047,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -8275,7 +8268,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -8296,7 +8289,7 @@ }, { "cell_type": "code", - "execution_count": 89, + "execution_count": 75, "metadata": {}, "outputs": [ { @@ -8335,7 +8328,7 @@ }, { "cell_type": "code", - "execution_count": 90, + "execution_count": 76, "metadata": { "collapsed": true }, @@ -8352,7 +8345,7 @@ }, { "cell_type": "code", - "execution_count": 91, + "execution_count": 86, "metadata": { "collapsed": true }, @@ -8369,7 +8362,7 @@ }, { "cell_type": "code", - "execution_count": 92, + "execution_count": 78, "metadata": { "scrolled": false }, @@ -8933,7 +8926,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -9154,7 +9147,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -9194,16 +9187,16 @@ }, { "cell_type": "code", - "execution_count": 93, + "execution_count": 79, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "3.2999999999999998" + "3.3999999999999999" ] }, - "execution_count": 93, + "execution_count": 79, "metadata": {}, "output_type": "execute_result" } @@ -9219,7 +9212,7 @@ }, { "cell_type": "code", - "execution_count": 94, + "execution_count": 80, "metadata": { "collapsed": true }, @@ -9237,16 +9230,16 @@ }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 81, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "3.746" + "3.6699999999999999" ] }, - "execution_count": 95, + "execution_count": 81, "metadata": {}, "output_type": "execute_result" } @@ -9259,16 +9252,16 @@ }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 82, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "2.3799999999999999" + "2.3900000000000001" ] }, - "execution_count": 96, + "execution_count": 82, "metadata": {}, "output_type": "execute_result" } @@ -9281,7 +9274,7 @@ }, { "cell_type": "code", - "execution_count": 98, + "execution_count": 83, "metadata": { "scrolled": true }, @@ -9290,15 +9283,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "2.0 11.288\n", - "4.0 9.059\n", - "6.0 6.884\n", - "8.0 5.385\n", - "10.0 3.452\n", - "12.0 2.33\n", - "14.0 1.412\n", - "16.0 0.764\n", - "18.0 0.382\n" + "2.0 11.098\n", + "4.0 8.85\n", + "6.0 6.812\n", + "8.0 5.345\n", + "10.0 3.465\n", + "12.0 2.373\n", + "14.0 1.407\n", + "16.0 0.725\n", + "18.0 0.44\n" ] } ], @@ -9312,7 +9305,7 @@ }, { "cell_type": "code", - "execution_count": 99, + "execution_count": 84, "metadata": {}, "outputs": [ { @@ -9874,7 +9867,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -10095,7 +10088,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -10127,6 +10120,33 @@ "outputs": [], "source": [] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": null, diff --git a/code/chap03-fig01.pdf b/code/chap03-fig01.pdf new file mode 100644 index 00000000..2118b1e4 Binary files /dev/null and b/code/chap03-fig01.pdf differ diff --git a/code/chap03-fig02.pdf b/code/chap03-fig02.pdf new file mode 100644 index 00000000..4f2f03da Binary files /dev/null and b/code/chap03-fig02.pdf differ diff --git a/code/chap03-fig03.pdf b/code/chap03-fig03.pdf new file mode 100644 index 00000000..af13f381 Binary files /dev/null and b/code/chap03-fig03.pdf differ diff --git a/code/chap03-fig04.pdf b/code/chap03-fig04.pdf new file mode 100644 index 00000000..ec512d5e Binary files /dev/null and b/code/chap03-fig04.pdf differ diff --git a/code/chap03-fig05.pdf b/code/chap03-fig05.pdf new file mode 100644 index 00000000..722abb67 Binary files /dev/null and b/code/chap03-fig05.pdf differ diff --git a/code/chap03.ipynb b/code/chap03.ipynb index f692fa8b..d90cad3c 100644 --- a/code/chap03.ipynb +++ b/code/chap03.ipynb @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": { "collapsed": true }, @@ -48,7 +48,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": { "collapsed": true }, @@ -66,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": { "collapsed": true }, @@ -87,11 +87,189 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
United States Census Bureau (2015)[18]Population Reference Bureau (1973–2015)[6]United Nations Department of Economic and Social Affairs (2015)[7]Maddison (2008)[8]HYDE (2007)[15]Tanton (1994)[9]Biraben (1980)[10]McEvedy & Jones (1978)[11]Thomlinson (1975)[12]Durand (1974)[13]Clark (1967)[14]
Year
195025576286542.516000e+0925251490002.544000e+092.527960e+092.400000e+092.527000e+092.500000e+092.400000e+09NaN2.486000e+09
19512594939877NaN25728509172.571663e+09NaNNaNNaNNaNNaNNaNNaN
19522636772306NaN26192920682.617949e+09NaNNaNNaNNaNNaNNaNNaN
19532682053389NaN26658653922.665959e+09NaNNaNNaNNaNNaNNaNNaN
19542730228104NaN27131720272.716927e+09NaNNaNNaNNaNNaNNaNNaN
\n", + "
" + ], + "text/plain": [ + " United States Census Bureau (2015)[18] \\\n", + "Year \n", + "1950 2557628654 \n", + "1951 2594939877 \n", + "1952 2636772306 \n", + "1953 2682053389 \n", + "1954 2730228104 \n", + "\n", + " Population Reference Bureau (1973–2015)[6] \\\n", + "Year \n", + "1950 2.516000e+09 \n", + "1951 NaN \n", + "1952 NaN \n", + "1953 NaN \n", + "1954 NaN \n", + "\n", + " United Nations Department of Economic and Social Affairs (2015)[7] \\\n", + "Year \n", + "1950 2525149000 \n", + "1951 2572850917 \n", + "1952 2619292068 \n", + "1953 2665865392 \n", + "1954 2713172027 \n", + "\n", + " Maddison (2008)[8] HYDE (2007)[15] Tanton (1994)[9] \\\n", + "Year \n", + "1950 2.544000e+09 2.527960e+09 2.400000e+09 \n", + "1951 2.571663e+09 NaN NaN \n", + "1952 2.617949e+09 NaN NaN \n", + "1953 2.665959e+09 NaN NaN \n", + "1954 2.716927e+09 NaN NaN \n", + "\n", + " Biraben (1980)[10] McEvedy & Jones (1978)[11] Thomlinson (1975)[12] \\\n", + "Year \n", + "1950 2.527000e+09 2.500000e+09 2.400000e+09 \n", + "1951 NaN NaN NaN \n", + "1952 NaN NaN NaN \n", + "1953 NaN NaN NaN \n", + "1954 NaN NaN NaN \n", + "\n", + " Durand (1974)[13] Clark (1967)[14] \n", + "Year \n", + "1950 NaN 2.486000e+09 \n", + "1951 NaN NaN \n", + "1952 NaN NaN \n", + "1953 NaN NaN \n", + "1954 NaN NaN " + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "table2 = tables[2]\n", "table2.head()" @@ -106,11 +284,189 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
United States Census Bureau (2015)[18]Population Reference Bureau (1973–2015)[6]United Nations Department of Economic and Social Affairs (2015)[7]Maddison (2008)[8]HYDE (2007)[15]Tanton (1994)[9]Biraben (1980)[10]McEvedy & Jones (1978)[11]Thomlinson (1975)[12]Durand (1974)[13]Clark (1967)[14]
Year
201169440555836.986951e+096997998760NaNNaNNaNNaNNaNNaNNaNNaN
201270223492837.057075e+097080072417NaNNaNNaNNaNNaNNaNNaNNaN
201371010278957.136796e+097162119434NaNNaNNaNNaNNaNNaNNaNNaN
201471787228937.238184e+097243784000NaNNaNNaNNaNNaNNaNNaNNaN
201572564900117.336435e+097349472000NaNNaNNaNNaNNaNNaNNaNNaN
\n", + "
" + ], + "text/plain": [ + " United States Census Bureau (2015)[18] \\\n", + "Year \n", + "2011 6944055583 \n", + "2012 7022349283 \n", + "2013 7101027895 \n", + "2014 7178722893 \n", + "2015 7256490011 \n", + "\n", + " Population Reference Bureau (1973–2015)[6] \\\n", + "Year \n", + "2011 6.986951e+09 \n", + "2012 7.057075e+09 \n", + "2013 7.136796e+09 \n", + "2014 7.238184e+09 \n", + "2015 7.336435e+09 \n", + "\n", + " United Nations Department of Economic and Social Affairs (2015)[7] \\\n", + "Year \n", + "2011 6997998760 \n", + "2012 7080072417 \n", + "2013 7162119434 \n", + "2014 7243784000 \n", + "2015 7349472000 \n", + "\n", + " Maddison (2008)[8] HYDE (2007)[15] Tanton (1994)[9] \\\n", + "Year \n", + "2011 NaN NaN NaN \n", + "2012 NaN NaN NaN \n", + "2013 NaN NaN NaN \n", + "2014 NaN NaN NaN \n", + "2015 NaN NaN NaN \n", + "\n", + " Biraben (1980)[10] McEvedy & Jones (1978)[11] Thomlinson (1975)[12] \\\n", + "Year \n", + "2011 NaN NaN NaN \n", + "2012 NaN NaN NaN \n", + "2013 NaN NaN NaN \n", + "2014 NaN NaN NaN \n", + "2015 NaN NaN NaN \n", + "\n", + " Durand (1974)[13] Clark (1967)[14] \n", + "Year \n", + "2011 NaN NaN \n", + "2012 NaN NaN \n", + "2013 NaN NaN \n", + "2014 NaN NaN \n", + "2015 NaN NaN " + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "table2.tail()" ] @@ -124,7 +480,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": { "collapsed": true }, @@ -148,9 +504,1118 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
censusprbunmaddisonhydetantonbirabenmjthomlinsondurandclark
Year
195025576286542.516000e+0925251490002.544000e+092.527960e+092.400000e+092.527000e+092.500000e+092.400000e+09NaN2.486000e+09
19512594939877NaN25728509172.571663e+09NaNNaNNaNNaNNaNNaNNaN
19522636772306NaN26192920682.617949e+09NaNNaNNaNNaNNaNNaNNaN
19532682053389NaN26658653922.665959e+09NaNNaNNaNNaNNaNNaNNaN
19542730228104NaN27131720272.716927e+09NaNNaNNaNNaNNaNNaNNaN
19552782098943NaN27616509812.769074e+09NaNNaNNaNNaNNaNNaNNaN
19562835299673NaN28115720312.822502e+09NaNNaNNaNNaNNaNNaNNaN
19572891349717NaN28630427952.879934e+09NaNNaNNaNNaNNaNNaNNaN
19582948137248NaN29160301672.939254e+09NaNNaNNaNNaNNaNNaNNaN
19593000716593NaN29703958142.995909e+09NaNNaNNaNNaNNaNNaNNaN
19603043001508NaN30260029423.041507e+093.042000e+09NaNNaNNaNNaNNaNNaN
19613083966929NaN30828302663.082161e+09NaNNaNNaNNaNNaNNaNNaN
19623140093217NaN31410715313.135787e+09NaNNaNNaNNaNNaNNaN3.036000e+09
19633209827882NaN32011782773.201354e+09NaNNaNNaNNaNNaNNaNNaN
19643281201306NaN32637388323.266477e+09NaNNaNNaNNaNNaNNaNNaN
19653350425793NaN33291224793.333138e+09NaNNaNNaNNaNNaNNaNNaN
19663420677923NaN33974752473.402224e+09NaNNaNNaNNaNNaNNaN3.288000e+09
19673490333715NaN34685217243.471464e+09NaNNaNNaNNaNNaNNaNNaN
19683562313822NaN35416748913.543086e+09NaNNaNNaNNaNNaNNaNNaN
19693637159050NaN36161087493.615743e+09NaNNaNNaNNaNNaNNaNNaN
19703712697742NaN36911726163.691157e+093.710000e+09NaN3.637000e+09NaN3.600000e+093,600,000,000– 3,700,000,0003.632000e+09
19713790326948NaN37667543453.769818e+09NaNNaNNaNNaNNaNNaNNaN
19723866568653NaN38428736113.846499e+09NaNNaNNaNNaNNaNNaNNaN
19733942096442NaN39191823323.922793e+093.923000e+09NaNNaNNaNNaNNaN3.860000e+09
19744016608813NaN39953049223.997677e+09NaNNaNNaNNaNNaNNaNNaN
19754089083233NaN40710204344.070671e+09NaNNaNNaN3.900000e+094.000000e+09NaNNaN
19764160185010NaN41461358504.141445e+09NaNNaNNaNNaNNaNNaNNaN
19774232084578NaN42208167374.213539e+09NaNNaNNaNNaNNaNNaNNaN
19784304105753NaN42956648254.286317e+09NaNNaNNaNNaNNaNNaNNaN
19794379013942NaN43715278714.363144e+09NaNNaNNaNNaNNaNNaNNaN
....................................
19864940571232NaN49533767104.920968e+09NaNNaNNaNNaNNaNNaNNaN
19875027200492NaN50453158715.006672e+09NaNNaNNaNNaNNaNNaNNaN
19885114557167NaN51382146885.093306e+09NaNNaNNaNNaNNaNNaNNaN
19895201440110NaN52300000005.180540e+09NaNNaNNaNNaNNaNNaNNaN
19905288955934NaN53208166675.269029e+095.308000e+09NaNNaNNaNNaNNaNNaN
19915371585922NaN54089087245.351922e+09NaNNaNNaNNaNNaNNaNNaN
19925456136278NaN54948995705.435722e+09NaNNaNNaNNaNNaNNaNNaN
19935538268316NaN55788651095.518127e+09NaNNaNNaNNaNNaNNaNNaN
19945618682132NaN56610863465.599396e+09NaNNaNNaNNaNNaNNaNNaN
199556992029855.760000e+0957418224125.681575e+09NaNNaNNaNNaNNaNNaNNaN
19965779440593NaN58210167505.762212e+09NaNNaNNaNNaNNaNNaNNaN
199758579725435.840000e+0958986883375.842122e+09NaNNaNNaNNaNNaNNaNNaN
19985935213248NaN59753036575.921366e+09NaNNaNNaNNaNNaNNaNNaN
19996012074922NaN60514780105.999622e+09NaNNaNNaNNaNNaNNaNNaN
200060885713836.067000e+0961277004286.076558e+096.145000e+09NaNNaN5.750000e+09NaNNaNNaN
200161652192476.137000e+0962041470266.154791e+09NaNNaNNaNNaNNaNNaNNaN
200262420163486.215000e+0962808538176.231704e+09NaNNaNNaNNaNNaNNaNNaN
200363185909566.314000e+0963579917496.308364e+09NaNNaNNaNNaNNaNNaNNaN
200463956995096.396000e+0964357055956.374056e+09NaNNaNNaNNaNNaNNaNNaN
200564730447326.477000e+0965140946056.462987e+09NaNNaNNaNNaNNaNNaNNaN
200665512635346.555000e+0965932279776.540214e+09NaNNaNNaNNaNNaNNaNNaN
200766299137596.625000e+0966731059376.616689e+09NaNNaNNaNNaNNaNNaNNaN
200867090497806.705000e+0967536492286.694832e+09NaNNaNNaNNaNNaNNaNNaN
200967882143946.809972e+0968347219336.764086e+09NaNNaNNaNNaNNaNNaNNaN
201068663323586.892319e+096916183482NaNNaNNaNNaNNaNNaNNaNNaN
201169440555836.986951e+096997998760NaNNaNNaNNaNNaNNaNNaNNaN
201270223492837.057075e+097080072417NaNNaNNaNNaNNaNNaNNaNNaN
201371010278957.136796e+097162119434NaNNaNNaNNaNNaNNaNNaNNaN
201471787228937.238184e+097243784000NaNNaNNaNNaNNaNNaNNaNNaN
201572564900117.336435e+097349472000NaNNaNNaNNaNNaNNaNNaNNaN
\n", + "

66 rows × 11 columns

\n", + "
" + ], + "text/plain": [ + " census prb un maddison hyde \\\n", + "Year \n", + "1950 2557628654 2.516000e+09 2525149000 2.544000e+09 2.527960e+09 \n", + "1951 2594939877 NaN 2572850917 2.571663e+09 NaN \n", + "1952 2636772306 NaN 2619292068 2.617949e+09 NaN \n", + "1953 2682053389 NaN 2665865392 2.665959e+09 NaN \n", + "1954 2730228104 NaN 2713172027 2.716927e+09 NaN \n", + "1955 2782098943 NaN 2761650981 2.769074e+09 NaN \n", + "1956 2835299673 NaN 2811572031 2.822502e+09 NaN \n", + "1957 2891349717 NaN 2863042795 2.879934e+09 NaN \n", + "1958 2948137248 NaN 2916030167 2.939254e+09 NaN \n", + "1959 3000716593 NaN 2970395814 2.995909e+09 NaN \n", + "1960 3043001508 NaN 3026002942 3.041507e+09 3.042000e+09 \n", + "1961 3083966929 NaN 3082830266 3.082161e+09 NaN \n", + "1962 3140093217 NaN 3141071531 3.135787e+09 NaN \n", + "1963 3209827882 NaN 3201178277 3.201354e+09 NaN \n", + "1964 3281201306 NaN 3263738832 3.266477e+09 NaN \n", + "1965 3350425793 NaN 3329122479 3.333138e+09 NaN \n", + "1966 3420677923 NaN 3397475247 3.402224e+09 NaN \n", + "1967 3490333715 NaN 3468521724 3.471464e+09 NaN \n", + "1968 3562313822 NaN 3541674891 3.543086e+09 NaN \n", + "1969 3637159050 NaN 3616108749 3.615743e+09 NaN \n", + "1970 3712697742 NaN 3691172616 3.691157e+09 3.710000e+09 \n", + "1971 3790326948 NaN 3766754345 3.769818e+09 NaN \n", + "1972 3866568653 NaN 3842873611 3.846499e+09 NaN \n", + "1973 3942096442 NaN 3919182332 3.922793e+09 3.923000e+09 \n", + "1974 4016608813 NaN 3995304922 3.997677e+09 NaN \n", + "1975 4089083233 NaN 4071020434 4.070671e+09 NaN \n", + "1976 4160185010 NaN 4146135850 4.141445e+09 NaN \n", + "1977 4232084578 NaN 4220816737 4.213539e+09 NaN \n", + "1978 4304105753 NaN 4295664825 4.286317e+09 NaN \n", + "1979 4379013942 NaN 4371527871 4.363144e+09 NaN \n", + "... ... ... ... ... ... \n", + "1986 4940571232 NaN 4953376710 4.920968e+09 NaN \n", + "1987 5027200492 NaN 5045315871 5.006672e+09 NaN \n", + "1988 5114557167 NaN 5138214688 5.093306e+09 NaN \n", + "1989 5201440110 NaN 5230000000 5.180540e+09 NaN \n", + "1990 5288955934 NaN 5320816667 5.269029e+09 5.308000e+09 \n", + "1991 5371585922 NaN 5408908724 5.351922e+09 NaN \n", + "1992 5456136278 NaN 5494899570 5.435722e+09 NaN \n", + "1993 5538268316 NaN 5578865109 5.518127e+09 NaN \n", + "1994 5618682132 NaN 5661086346 5.599396e+09 NaN \n", + "1995 5699202985 5.760000e+09 5741822412 5.681575e+09 NaN \n", + "1996 5779440593 NaN 5821016750 5.762212e+09 NaN \n", + "1997 5857972543 5.840000e+09 5898688337 5.842122e+09 NaN \n", + "1998 5935213248 NaN 5975303657 5.921366e+09 NaN \n", + "1999 6012074922 NaN 6051478010 5.999622e+09 NaN \n", + "2000 6088571383 6.067000e+09 6127700428 6.076558e+09 6.145000e+09 \n", + "2001 6165219247 6.137000e+09 6204147026 6.154791e+09 NaN \n", + "2002 6242016348 6.215000e+09 6280853817 6.231704e+09 NaN \n", + "2003 6318590956 6.314000e+09 6357991749 6.308364e+09 NaN \n", + "2004 6395699509 6.396000e+09 6435705595 6.374056e+09 NaN \n", + "2005 6473044732 6.477000e+09 6514094605 6.462987e+09 NaN \n", + "2006 6551263534 6.555000e+09 6593227977 6.540214e+09 NaN \n", + "2007 6629913759 6.625000e+09 6673105937 6.616689e+09 NaN \n", + "2008 6709049780 6.705000e+09 6753649228 6.694832e+09 NaN \n", + "2009 6788214394 6.809972e+09 6834721933 6.764086e+09 NaN \n", + "2010 6866332358 6.892319e+09 6916183482 NaN NaN \n", + "2011 6944055583 6.986951e+09 6997998760 NaN NaN \n", + "2012 7022349283 7.057075e+09 7080072417 NaN NaN \n", + "2013 7101027895 7.136796e+09 7162119434 NaN NaN \n", + "2014 7178722893 7.238184e+09 7243784000 NaN NaN \n", + "2015 7256490011 7.336435e+09 7349472000 NaN NaN \n", + "\n", + " tanton biraben mj thomlinson \\\n", + "Year \n", + "1950 2.400000e+09 2.527000e+09 2.500000e+09 2.400000e+09 \n", + "1951 NaN NaN NaN NaN \n", + "1952 NaN NaN NaN NaN \n", + "1953 NaN NaN NaN NaN \n", + "1954 NaN NaN NaN NaN \n", + "1955 NaN NaN NaN NaN \n", + "1956 NaN NaN NaN NaN \n", + "1957 NaN NaN NaN NaN \n", + "1958 NaN NaN NaN NaN \n", + "1959 NaN NaN NaN NaN \n", + "1960 NaN NaN NaN NaN \n", + "1961 NaN NaN NaN NaN \n", + "1962 NaN NaN NaN NaN \n", + "1963 NaN NaN NaN NaN \n", + "1964 NaN NaN NaN NaN \n", + "1965 NaN NaN NaN NaN \n", + "1966 NaN NaN NaN NaN \n", + "1967 NaN NaN NaN NaN \n", + "1968 NaN NaN NaN NaN \n", + "1969 NaN NaN NaN NaN \n", + "1970 NaN 3.637000e+09 NaN 3.600000e+09 \n", + "1971 NaN NaN NaN NaN \n", + "1972 NaN NaN NaN NaN \n", + "1973 NaN NaN NaN NaN \n", + "1974 NaN NaN NaN NaN \n", + "1975 NaN NaN 3.900000e+09 4.000000e+09 \n", + "1976 NaN NaN NaN NaN \n", + "1977 NaN NaN NaN NaN \n", + "1978 NaN NaN NaN NaN \n", + "1979 NaN NaN NaN NaN \n", + "... ... ... ... ... \n", + "1986 NaN NaN NaN NaN \n", + "1987 NaN NaN NaN NaN \n", + "1988 NaN NaN NaN NaN \n", + "1989 NaN NaN NaN NaN \n", + "1990 NaN NaN NaN NaN \n", + "1991 NaN NaN NaN NaN \n", + "1992 NaN NaN NaN NaN \n", + "1993 NaN NaN NaN NaN \n", + "1994 NaN NaN NaN NaN \n", + "1995 NaN NaN NaN NaN \n", + "1996 NaN NaN NaN NaN \n", + "1997 NaN NaN NaN NaN \n", + "1998 NaN NaN NaN NaN \n", + "1999 NaN NaN NaN NaN \n", + "2000 NaN NaN 5.750000e+09 NaN \n", + "2001 NaN NaN NaN NaN \n", + "2002 NaN NaN NaN NaN \n", + "2003 NaN NaN NaN NaN \n", + "2004 NaN NaN NaN NaN \n", + "2005 NaN NaN NaN NaN \n", + "2006 NaN NaN NaN NaN \n", + "2007 NaN NaN NaN NaN \n", + "2008 NaN NaN NaN NaN \n", + "2009 NaN NaN NaN NaN \n", + "2010 NaN NaN NaN NaN \n", + "2011 NaN NaN NaN NaN \n", + "2012 NaN NaN NaN NaN \n", + "2013 NaN NaN NaN NaN \n", + "2014 NaN NaN NaN NaN \n", + "2015 NaN NaN NaN NaN \n", + "\n", + " durand clark \n", + "Year \n", + "1950 NaN 2.486000e+09 \n", + "1951 NaN NaN \n", + "1952 NaN NaN \n", + "1953 NaN NaN \n", + "1954 NaN NaN \n", + "1955 NaN NaN \n", + "1956 NaN NaN \n", + "1957 NaN NaN \n", + "1958 NaN NaN \n", + "1959 NaN NaN \n", + "1960 NaN NaN \n", + "1961 NaN NaN \n", + "1962 NaN 3.036000e+09 \n", + "1963 NaN NaN \n", + "1964 NaN NaN \n", + "1965 NaN NaN \n", + "1966 NaN 3.288000e+09 \n", + "1967 NaN NaN \n", + "1968 NaN NaN \n", + "1969 NaN NaN \n", + "1970 3,600,000,000– 3,700,000,000 3.632000e+09 \n", + "1971 NaN NaN \n", + "1972 NaN NaN \n", + "1973 NaN 3.860000e+09 \n", + "1974 NaN NaN \n", + "1975 NaN NaN \n", + "1976 NaN NaN \n", + "1977 NaN NaN \n", + "1978 NaN NaN \n", + "1979 NaN NaN \n", + "... ... ... \n", + "1986 NaN NaN \n", + "1987 NaN NaN \n", + "1988 NaN NaN \n", + "1989 NaN NaN \n", + "1990 NaN NaN \n", + "1991 NaN NaN \n", + "1992 NaN NaN \n", + "1993 NaN NaN \n", + "1994 NaN NaN \n", + "1995 NaN NaN \n", + "1996 NaN NaN \n", + "1997 NaN NaN \n", + "1998 NaN NaN \n", + "1999 NaN NaN \n", + "2000 NaN NaN \n", + "2001 NaN NaN \n", + "2002 NaN NaN \n", + "2003 NaN NaN \n", + "2004 NaN NaN \n", + "2005 NaN NaN \n", + "2006 NaN NaN \n", + "2007 NaN NaN \n", + "2008 NaN NaN \n", + "2009 NaN NaN \n", + "2010 NaN NaN \n", + "2011 NaN NaN \n", + "2012 NaN NaN \n", + "2013 NaN NaN \n", + "2014 NaN NaN \n", + "2015 NaN NaN \n", + "\n", + "[66 rows x 11 columns]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "table2" ] @@ -164,9 +1629,82 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "Year\n", + "1950 2557628654\n", + "1951 2594939877\n", + "1952 2636772306\n", + "1953 2682053389\n", + "1954 2730228104\n", + "1955 2782098943\n", + "1956 2835299673\n", + "1957 2891349717\n", + "1958 2948137248\n", + "1959 3000716593\n", + "1960 3043001508\n", + "1961 3083966929\n", + "1962 3140093217\n", + "1963 3209827882\n", + "1964 3281201306\n", + "1965 3350425793\n", + "1966 3420677923\n", + "1967 3490333715\n", + "1968 3562313822\n", + "1969 3637159050\n", + "1970 3712697742\n", + "1971 3790326948\n", + "1972 3866568653\n", + "1973 3942096442\n", + "1974 4016608813\n", + "1975 4089083233\n", + "1976 4160185010\n", + "1977 4232084578\n", + "1978 4304105753\n", + "1979 4379013942\n", + " ... \n", + "1986 4940571232\n", + "1987 5027200492\n", + "1988 5114557167\n", + "1989 5201440110\n", + "1990 5288955934\n", + "1991 5371585922\n", + "1992 5456136278\n", + "1993 5538268316\n", + "1994 5618682132\n", + "1995 5699202985\n", + "1996 5779440593\n", + "1997 5857972543\n", + "1998 5935213248\n", + "1999 6012074922\n", + "2000 6088571383\n", + "2001 6165219247\n", + "2002 6242016348\n", + "2003 6318590956\n", + "2004 6395699509\n", + "2005 6473044732\n", + "2006 6551263534\n", + "2007 6629913759\n", + "2008 6709049780\n", + "2009 6788214394\n", + "2010 6866332358\n", + "2011 6944055583\n", + "2012 7022349283\n", + "2013 7101027895\n", + "2014 7178722893\n", + "2015 7256490011\n", + "Name: census, Length: 66, dtype: int64" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "census = table2.census\n", "census" @@ -183,9 +1721,33 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([2557628654, 2594939877, 2636772306, 2682053389, 2730228104,\n", + " 2782098943, 2835299673, 2891349717, 2948137248, 3000716593,\n", + " 3043001508, 3083966929, 3140093217, 3209827882, 3281201306,\n", + " 3350425793, 3420677923, 3490333715, 3562313822, 3637159050,\n", + " 3712697742, 3790326948, 3866568653, 3942096442, 4016608813,\n", + " 4089083233, 4160185010, 4232084578, 4304105753, 4379013942,\n", + " 4451362735, 4534410125, 4614566561, 4695736743, 4774569391,\n", + " 4856462699, 4940571232, 5027200492, 5114557167, 5201440110,\n", + " 5288955934, 5371585922, 5456136278, 5538268316, 5618682132,\n", + " 5699202985, 5779440593, 5857972543, 5935213248, 6012074922,\n", + " 6088571383, 6165219247, 6242016348, 6318590956, 6395699509,\n", + " 6473044732, 6551263534, 6629913759, 6709049780, 6788214394,\n", + " 6866332358, 6944055583, 7022349283, 7101027895, 7178722893,\n", + " 7256490011], dtype=int64)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "census.values" ] @@ -199,9 +1761,26 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "Int64Index([1950, 1951, 1952, 1953, 1954, 1955, 1956, 1957, 1958, 1959, 1960,\n", + " 1961, 1962, 1963, 1964, 1965, 1966, 1967, 1968, 1969, 1970, 1971,\n", + " 1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1980, 1981, 1982,\n", + " 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993,\n", + " 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,\n", + " 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015],\n", + " dtype='int64', name='Year')" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "census.index" ] @@ -221,36 +1800,80 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "pandas.core.frame.DataFrame" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "type(table2)" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "pandas.core.series.Series" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "type(census)" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "pandas.core.indexes.numeric.Int64Index" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "type(census.index)" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "numpy.ndarray" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "type(census.values)" ] @@ -266,7 +1889,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": { "collapsed": true }, @@ -298,6 +1921,7 @@ "cell_type": "code", "execution_count": 17, "metadata": { + "collapsed": true, "scrolled": false }, "outputs": [], @@ -348,7 +1972,9 @@ { "cell_type": "code", "execution_count": 20, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "max(abs(census - un) / un) * 100" @@ -369,6 +1995,7 @@ "cell_type": "code", "execution_count": 21, "metadata": { + "collapsed": true, "scrolled": true }, "outputs": [], @@ -380,6 +2007,7 @@ "cell_type": "code", "execution_count": 22, "metadata": { + "collapsed": true, "scrolled": true }, "outputs": [], @@ -391,6 +2019,7 @@ "cell_type": "code", "execution_count": 23, "metadata": { + "collapsed": true, "scrolled": true }, "outputs": [], @@ -401,7 +2030,9 @@ { "cell_type": "code", "execution_count": 24, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# Solution goes here" @@ -424,7 +2055,9 @@ { "cell_type": "code", "execution_count": 25, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "census[1950]" @@ -440,7 +2073,9 @@ { "cell_type": "code", "execution_count": 26, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "census[2015]" @@ -456,7 +2091,9 @@ { "cell_type": "code", "execution_count": 27, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "first_year = census.index[0]\n", @@ -476,7 +2113,9 @@ { "cell_type": "code", "execution_count": 28, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "total_growth = census[last_year] - census[first_year]\n", @@ -513,7 +2152,9 @@ { "cell_type": "code", "execution_count": 30, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "results[1950] = census[1950]\n", @@ -549,7 +2190,9 @@ { "cell_type": "code", "execution_count": 32, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "newfig()\n", @@ -580,7 +2223,9 @@ { "cell_type": "code", "execution_count": 33, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# Solution goes here" @@ -703,7 +2348,9 @@ { "cell_type": "code", "execution_count": 38, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "run_simulation1(system)\n", @@ -720,7 +2367,9 @@ { "cell_type": "code", "execution_count": 39, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "help(decorate)" @@ -730,6 +2379,7 @@ "cell_type": "code", "execution_count": 40, "metadata": { + "collapsed": true, "scrolled": true }, "outputs": [], @@ -762,7 +2412,9 @@ { "cell_type": "code", "execution_count": 42, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# Solution goes here" @@ -835,7 +2487,9 @@ { "cell_type": "code", "execution_count": 45, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "run_simulation2(system)\n", @@ -875,7 +2529,9 @@ { "cell_type": "code", "execution_count": 47, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# Solution goes here" @@ -927,7 +2583,9 @@ { "cell_type": "code", "execution_count": 49, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "update_func1" @@ -943,7 +2601,9 @@ { "cell_type": "code", "execution_count": 50, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "type(update_func1)" @@ -1007,7 +2667,9 @@ { "cell_type": "code", "execution_count": 53, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "plot_results(system, title='Proportional model, factored')" @@ -1065,7 +2727,9 @@ { "cell_type": "code", "execution_count": 55, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "system.alpha = system.birth_rate - system.death_rate\n", @@ -1100,7 +2764,9 @@ { "cell_type": "code", "execution_count": 57, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# Solution goes here" @@ -1151,7 +2817,9 @@ { "cell_type": "code", "execution_count": 59, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "system.alpha = 0.025\n", @@ -1193,7 +2861,9 @@ { "cell_type": "code", "execution_count": 61, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "newfig()\n", @@ -1233,7 +2903,9 @@ { "cell_type": "code", "execution_count": 63, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "-system.alpha / system.beta" @@ -1260,7 +2932,9 @@ { "cell_type": "code", "execution_count": 64, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# Solution goes here" @@ -1280,7 +2954,9 @@ { "cell_type": "code", "execution_count": 66, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# Solution goes here" @@ -1303,7 +2979,9 @@ { "cell_type": "code", "execution_count": 67, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "table1 = tables[1]\n", @@ -1320,7 +2998,9 @@ { "cell_type": "code", "execution_count": 68, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "table1.tail()" @@ -1402,6 +3082,7 @@ "cell_type": "code", "execution_count": 72, "metadata": { + "collapsed": true, "scrolled": false }, "outputs": [], @@ -1423,7 +3104,9 @@ { "cell_type": "code", "execution_count": 73, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "newfig()\n", @@ -1456,7 +3139,9 @@ { "cell_type": "code", "execution_count": 75, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# Solution goes here" @@ -1465,7 +3150,9 @@ { "cell_type": "code", "execution_count": 76, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# Solution goes here" @@ -1483,7 +3170,9 @@ { "cell_type": "code", "execution_count": 78, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def carrying_capacity(system):\n", @@ -1498,7 +3187,9 @@ { "cell_type": "code", "execution_count": 80, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def carrying_capacity():\n", @@ -1513,7 +3204,9 @@ { "cell_type": "code", "execution_count": 81, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def carrying_capacity(system):\n", @@ -1529,7 +3222,9 @@ { "cell_type": "code", "execution_count": 82, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def carrying_capacity(system):\n", @@ -1543,7 +3238,9 @@ { "cell_type": "code", "execution_count": 83, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def carrying_capacity(system):\n", diff --git a/code/chap03mine.ipynb b/code/chap03mine.ipynb new file mode 100644 index 00000000..27b32865 --- /dev/null +++ b/code/chap03mine.ipynb @@ -0,0 +1,17431 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Modeling and Simulation in Python\n", + "\n", + "Chapter 3: Explain\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": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# If you want the figures to appear in the notebook, \n", + "# and you want to interact with them, use\n", + "# %matplotlib notebook\n", + "\n", + "# If you want the figures to appear in the notebook, \n", + "# and you don't want to interact with them, use\n", + "# %matplotlib inline\n", + "\n", + "# If you want the figures to appear in separate windows, use\n", + "# %matplotlib qt5\n", + "\n", + "# To switch from one to another, you have to select Kernel->Restart\n", + "\n", + "%matplotlib notebook\n", + "\n", + "from modsim import *" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Pandas is a module that provides tools for reading and processing data. The `read_html` reads a web page from a file or the Internet and creates one DataFrame for each table on the page." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from pandas import read_html" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The data directory contains a downloaded copy of https://en.wikipedia.org/wiki/World_population_estimates" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "filename = 'data/World_population_estimates.html'\n", + "tables = read_html(filename, header=0, index_col=0, decimal='M')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`tables` is a sequence of DataFrame objects. We can select the DataFrame we want using the bracket operator. The tables are numbered from 0, so `table2` is actually the third table on the page.\n", + "\n", + "`head` selects the header and the first five rows." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
United States Census Bureau (2015)[18]Population Reference Bureau (1973–2015)[6]United Nations Department of Economic and Social Affairs (2015)[7]Maddison (2008)[8]HYDE (2007)[15]Tanton (1994)[9]Biraben (1980)[10]McEvedy & Jones (1978)[11]Thomlinson (1975)[12]Durand (1974)[13]Clark (1967)[14]
Year
195025576286542.516000e+0925251490002.544000e+092.527960e+092.400000e+092.527000e+092.500000e+092.400000e+09NaN2.486000e+09
19512594939877NaN25728509172.571663e+09NaNNaNNaNNaNNaNNaNNaN
19522636772306NaN26192920682.617949e+09NaNNaNNaNNaNNaNNaNNaN
19532682053389NaN26658653922.665959e+09NaNNaNNaNNaNNaNNaNNaN
19542730228104NaN27131720272.716927e+09NaNNaNNaNNaNNaNNaNNaN
\n", + "
" + ], + "text/plain": [ + " United States Census Bureau (2015)[18] \\\n", + "Year \n", + "1950 2557628654 \n", + "1951 2594939877 \n", + "1952 2636772306 \n", + "1953 2682053389 \n", + "1954 2730228104 \n", + "\n", + " Population Reference Bureau (1973–2015)[6] \\\n", + "Year \n", + "1950 2.516000e+09 \n", + "1951 NaN \n", + "1952 NaN \n", + "1953 NaN \n", + "1954 NaN \n", + "\n", + " United Nations Department of Economic and Social Affairs (2015)[7] \\\n", + "Year \n", + "1950 2525149000 \n", + "1951 2572850917 \n", + "1952 2619292068 \n", + "1953 2665865392 \n", + "1954 2713172027 \n", + "\n", + " Maddison (2008)[8] HYDE (2007)[15] Tanton (1994)[9] \\\n", + "Year \n", + "1950 2.544000e+09 2.527960e+09 2.400000e+09 \n", + "1951 2.571663e+09 NaN NaN \n", + "1952 2.617949e+09 NaN NaN \n", + "1953 2.665959e+09 NaN NaN \n", + "1954 2.716927e+09 NaN NaN \n", + "\n", + " Biraben (1980)[10] McEvedy & Jones (1978)[11] Thomlinson (1975)[12] \\\n", + "Year \n", + "1950 2.527000e+09 2.500000e+09 2.400000e+09 \n", + "1951 NaN NaN NaN \n", + "1952 NaN NaN NaN \n", + "1953 NaN NaN NaN \n", + "1954 NaN NaN NaN \n", + "\n", + " Durand (1974)[13] Clark (1967)[14] \n", + "Year \n", + "1950 NaN 2.486000e+09 \n", + "1951 NaN NaN \n", + "1952 NaN NaN \n", + "1953 NaN NaN \n", + "1954 NaN NaN " + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "table2 = tables[2\n", + " ]\n", + "table2.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`tail` selects the last five rows." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
United States Census Bureau (2015)[18]Population Reference Bureau (1973–2015)[6]United Nations Department of Economic and Social Affairs (2015)[7]Maddison (2008)[8]HYDE (2007)[15]Tanton (1994)[9]Biraben (1980)[10]McEvedy & Jones (1978)[11]Thomlinson (1975)[12]Durand (1974)[13]Clark (1967)[14]
Year
201169440555836.986951e+096997998760NaNNaNNaNNaNNaNNaNNaNNaN
201270223492837.057075e+097080072417NaNNaNNaNNaNNaNNaNNaNNaN
201371010278957.136796e+097162119434NaNNaNNaNNaNNaNNaNNaNNaN
201471787228937.238184e+097243784000NaNNaNNaNNaNNaNNaNNaNNaN
201572564900117.336435e+097349472000NaNNaNNaNNaNNaNNaNNaNNaN
\n", + "
" + ], + "text/plain": [ + " United States Census Bureau (2015)[18] \\\n", + "Year \n", + "2011 6944055583 \n", + "2012 7022349283 \n", + "2013 7101027895 \n", + "2014 7178722893 \n", + "2015 7256490011 \n", + "\n", + " Population Reference Bureau (1973–2015)[6] \\\n", + "Year \n", + "2011 6.986951e+09 \n", + "2012 7.057075e+09 \n", + "2013 7.136796e+09 \n", + "2014 7.238184e+09 \n", + "2015 7.336435e+09 \n", + "\n", + " United Nations Department of Economic and Social Affairs (2015)[7] \\\n", + "Year \n", + "2011 6997998760 \n", + "2012 7080072417 \n", + "2013 7162119434 \n", + "2014 7243784000 \n", + "2015 7349472000 \n", + "\n", + " Maddison (2008)[8] HYDE (2007)[15] Tanton (1994)[9] \\\n", + "Year \n", + "2011 NaN NaN NaN \n", + "2012 NaN NaN NaN \n", + "2013 NaN NaN NaN \n", + "2014 NaN NaN NaN \n", + "2015 NaN NaN NaN \n", + "\n", + " Biraben (1980)[10] McEvedy & Jones (1978)[11] Thomlinson (1975)[12] \\\n", + "Year \n", + "2011 NaN NaN NaN \n", + "2012 NaN NaN NaN \n", + "2013 NaN NaN NaN \n", + "2014 NaN NaN NaN \n", + "2015 NaN NaN NaN \n", + "\n", + " Durand (1974)[13] Clark (1967)[14] \n", + "Year \n", + "2011 NaN NaN \n", + "2012 NaN NaN \n", + "2013 NaN NaN \n", + "2014 NaN NaN \n", + "2015 NaN NaN " + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "table2.tail()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Long column names are awkard to work with, but we can replace them with abbreviated names." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "table2.columns = ['census', 'prb', 'un', 'maddison', \n", + " 'hyde', 'tanton', 'biraben', 'mj', \n", + " 'thomlinson', 'durand', 'clark']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's what the DataFrame looks like now. \n", + "\n", + "Some of the values use scientific notation; for example, `2.544000e+09` is shorthand for $2.544 \\cdot 10^9$ or 2.544 billion.\n", + "\n", + "`NaN` is a special value that indicates missing data." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
censusprbunmaddisonhydetantonbirabenmjthomlinsondurandclark
Year
195025576286542.516000e+0925251490002.544000e+092.527960e+092.400000e+092.527000e+092.500000e+092.400000e+09NaN2.486000e+09
19512594939877NaN25728509172.571663e+09NaNNaNNaNNaNNaNNaNNaN
19522636772306NaN26192920682.617949e+09NaNNaNNaNNaNNaNNaNNaN
19532682053389NaN26658653922.665959e+09NaNNaNNaNNaNNaNNaNNaN
19542730228104NaN27131720272.716927e+09NaNNaNNaNNaNNaNNaNNaN
19552782098943NaN27616509812.769074e+09NaNNaNNaNNaNNaNNaNNaN
19562835299673NaN28115720312.822502e+09NaNNaNNaNNaNNaNNaNNaN
19572891349717NaN28630427952.879934e+09NaNNaNNaNNaNNaNNaNNaN
19582948137248NaN29160301672.939254e+09NaNNaNNaNNaNNaNNaNNaN
19593000716593NaN29703958142.995909e+09NaNNaNNaNNaNNaNNaNNaN
19603043001508NaN30260029423.041507e+093.042000e+09NaNNaNNaNNaNNaNNaN
19613083966929NaN30828302663.082161e+09NaNNaNNaNNaNNaNNaNNaN
19623140093217NaN31410715313.135787e+09NaNNaNNaNNaNNaNNaN3.036000e+09
19633209827882NaN32011782773.201354e+09NaNNaNNaNNaNNaNNaNNaN
19643281201306NaN32637388323.266477e+09NaNNaNNaNNaNNaNNaNNaN
19653350425793NaN33291224793.333138e+09NaNNaNNaNNaNNaNNaNNaN
19663420677923NaN33974752473.402224e+09NaNNaNNaNNaNNaNNaN3.288000e+09
19673490333715NaN34685217243.471464e+09NaNNaNNaNNaNNaNNaNNaN
19683562313822NaN35416748913.543086e+09NaNNaNNaNNaNNaNNaNNaN
19693637159050NaN36161087493.615743e+09NaNNaNNaNNaNNaNNaNNaN
19703712697742NaN36911726163.691157e+093.710000e+09NaN3.637000e+09NaN3.600000e+093,600,000,000– 3,700,000,0003.632000e+09
19713790326948NaN37667543453.769818e+09NaNNaNNaNNaNNaNNaNNaN
19723866568653NaN38428736113.846499e+09NaNNaNNaNNaNNaNNaNNaN
19733942096442NaN39191823323.922793e+093.923000e+09NaNNaNNaNNaNNaN3.860000e+09
19744016608813NaN39953049223.997677e+09NaNNaNNaNNaNNaNNaNNaN
19754089083233NaN40710204344.070671e+09NaNNaNNaN3.900000e+094.000000e+09NaNNaN
19764160185010NaN41461358504.141445e+09NaNNaNNaNNaNNaNNaNNaN
19774232084578NaN42208167374.213539e+09NaNNaNNaNNaNNaNNaNNaN
19784304105753NaN42956648254.286317e+09NaNNaNNaNNaNNaNNaNNaN
19794379013942NaN43715278714.363144e+09NaNNaNNaNNaNNaNNaNNaN
....................................
19864940571232NaN49533767104.920968e+09NaNNaNNaNNaNNaNNaNNaN
19875027200492NaN50453158715.006672e+09NaNNaNNaNNaNNaNNaNNaN
19885114557167NaN51382146885.093306e+09NaNNaNNaNNaNNaNNaNNaN
19895201440110NaN52300000005.180540e+09NaNNaNNaNNaNNaNNaNNaN
19905288955934NaN53208166675.269029e+095.308000e+09NaNNaNNaNNaNNaNNaN
19915371585922NaN54089087245.351922e+09NaNNaNNaNNaNNaNNaNNaN
19925456136278NaN54948995705.435722e+09NaNNaNNaNNaNNaNNaNNaN
19935538268316NaN55788651095.518127e+09NaNNaNNaNNaNNaNNaNNaN
19945618682132NaN56610863465.599396e+09NaNNaNNaNNaNNaNNaNNaN
199556992029855.760000e+0957418224125.681575e+09NaNNaNNaNNaNNaNNaNNaN
19965779440593NaN58210167505.762212e+09NaNNaNNaNNaNNaNNaNNaN
199758579725435.840000e+0958986883375.842122e+09NaNNaNNaNNaNNaNNaNNaN
19985935213248NaN59753036575.921366e+09NaNNaNNaNNaNNaNNaNNaN
19996012074922NaN60514780105.999622e+09NaNNaNNaNNaNNaNNaNNaN
200060885713836.067000e+0961277004286.076558e+096.145000e+09NaNNaN5.750000e+09NaNNaNNaN
200161652192476.137000e+0962041470266.154791e+09NaNNaNNaNNaNNaNNaNNaN
200262420163486.215000e+0962808538176.231704e+09NaNNaNNaNNaNNaNNaNNaN
200363185909566.314000e+0963579917496.308364e+09NaNNaNNaNNaNNaNNaNNaN
200463956995096.396000e+0964357055956.374056e+09NaNNaNNaNNaNNaNNaNNaN
200564730447326.477000e+0965140946056.462987e+09NaNNaNNaNNaNNaNNaNNaN
200665512635346.555000e+0965932279776.540214e+09NaNNaNNaNNaNNaNNaNNaN
200766299137596.625000e+0966731059376.616689e+09NaNNaNNaNNaNNaNNaNNaN
200867090497806.705000e+0967536492286.694832e+09NaNNaNNaNNaNNaNNaNNaN
200967882143946.809972e+0968347219336.764086e+09NaNNaNNaNNaNNaNNaNNaN
201068663323586.892319e+096916183482NaNNaNNaNNaNNaNNaNNaNNaN
201169440555836.986951e+096997998760NaNNaNNaNNaNNaNNaNNaNNaN
201270223492837.057075e+097080072417NaNNaNNaNNaNNaNNaNNaNNaN
201371010278957.136796e+097162119434NaNNaNNaNNaNNaNNaNNaNNaN
201471787228937.238184e+097243784000NaNNaNNaNNaNNaNNaNNaNNaN
201572564900117.336435e+097349472000NaNNaNNaNNaNNaNNaNNaNNaN
\n", + "

66 rows × 11 columns

\n", + "
" + ], + "text/plain": [ + " census prb un maddison hyde \\\n", + "Year \n", + "1950 2557628654 2.516000e+09 2525149000 2.544000e+09 2.527960e+09 \n", + "1951 2594939877 NaN 2572850917 2.571663e+09 NaN \n", + "1952 2636772306 NaN 2619292068 2.617949e+09 NaN \n", + "1953 2682053389 NaN 2665865392 2.665959e+09 NaN \n", + "1954 2730228104 NaN 2713172027 2.716927e+09 NaN \n", + "1955 2782098943 NaN 2761650981 2.769074e+09 NaN \n", + "1956 2835299673 NaN 2811572031 2.822502e+09 NaN \n", + "1957 2891349717 NaN 2863042795 2.879934e+09 NaN \n", + "1958 2948137248 NaN 2916030167 2.939254e+09 NaN \n", + "1959 3000716593 NaN 2970395814 2.995909e+09 NaN \n", + "1960 3043001508 NaN 3026002942 3.041507e+09 3.042000e+09 \n", + "1961 3083966929 NaN 3082830266 3.082161e+09 NaN \n", + "1962 3140093217 NaN 3141071531 3.135787e+09 NaN \n", + "1963 3209827882 NaN 3201178277 3.201354e+09 NaN \n", + "1964 3281201306 NaN 3263738832 3.266477e+09 NaN \n", + "1965 3350425793 NaN 3329122479 3.333138e+09 NaN \n", + "1966 3420677923 NaN 3397475247 3.402224e+09 NaN \n", + "1967 3490333715 NaN 3468521724 3.471464e+09 NaN \n", + "1968 3562313822 NaN 3541674891 3.543086e+09 NaN \n", + "1969 3637159050 NaN 3616108749 3.615743e+09 NaN \n", + "1970 3712697742 NaN 3691172616 3.691157e+09 3.710000e+09 \n", + "1971 3790326948 NaN 3766754345 3.769818e+09 NaN \n", + "1972 3866568653 NaN 3842873611 3.846499e+09 NaN \n", + "1973 3942096442 NaN 3919182332 3.922793e+09 3.923000e+09 \n", + "1974 4016608813 NaN 3995304922 3.997677e+09 NaN \n", + "1975 4089083233 NaN 4071020434 4.070671e+09 NaN \n", + "1976 4160185010 NaN 4146135850 4.141445e+09 NaN \n", + "1977 4232084578 NaN 4220816737 4.213539e+09 NaN \n", + "1978 4304105753 NaN 4295664825 4.286317e+09 NaN \n", + "1979 4379013942 NaN 4371527871 4.363144e+09 NaN \n", + "... ... ... ... ... ... \n", + "1986 4940571232 NaN 4953376710 4.920968e+09 NaN \n", + "1987 5027200492 NaN 5045315871 5.006672e+09 NaN \n", + "1988 5114557167 NaN 5138214688 5.093306e+09 NaN \n", + "1989 5201440110 NaN 5230000000 5.180540e+09 NaN \n", + "1990 5288955934 NaN 5320816667 5.269029e+09 5.308000e+09 \n", + "1991 5371585922 NaN 5408908724 5.351922e+09 NaN \n", + "1992 5456136278 NaN 5494899570 5.435722e+09 NaN \n", + "1993 5538268316 NaN 5578865109 5.518127e+09 NaN \n", + "1994 5618682132 NaN 5661086346 5.599396e+09 NaN \n", + "1995 5699202985 5.760000e+09 5741822412 5.681575e+09 NaN \n", + "1996 5779440593 NaN 5821016750 5.762212e+09 NaN \n", + "1997 5857972543 5.840000e+09 5898688337 5.842122e+09 NaN \n", + "1998 5935213248 NaN 5975303657 5.921366e+09 NaN \n", + "1999 6012074922 NaN 6051478010 5.999622e+09 NaN \n", + "2000 6088571383 6.067000e+09 6127700428 6.076558e+09 6.145000e+09 \n", + "2001 6165219247 6.137000e+09 6204147026 6.154791e+09 NaN \n", + "2002 6242016348 6.215000e+09 6280853817 6.231704e+09 NaN \n", + "2003 6318590956 6.314000e+09 6357991749 6.308364e+09 NaN \n", + "2004 6395699509 6.396000e+09 6435705595 6.374056e+09 NaN \n", + "2005 6473044732 6.477000e+09 6514094605 6.462987e+09 NaN \n", + "2006 6551263534 6.555000e+09 6593227977 6.540214e+09 NaN \n", + "2007 6629913759 6.625000e+09 6673105937 6.616689e+09 NaN \n", + "2008 6709049780 6.705000e+09 6753649228 6.694832e+09 NaN \n", + "2009 6788214394 6.809972e+09 6834721933 6.764086e+09 NaN \n", + "2010 6866332358 6.892319e+09 6916183482 NaN NaN \n", + "2011 6944055583 6.986951e+09 6997998760 NaN NaN \n", + "2012 7022349283 7.057075e+09 7080072417 NaN NaN \n", + "2013 7101027895 7.136796e+09 7162119434 NaN NaN \n", + "2014 7178722893 7.238184e+09 7243784000 NaN NaN \n", + "2015 7256490011 7.336435e+09 7349472000 NaN NaN \n", + "\n", + " tanton biraben mj thomlinson \\\n", + "Year \n", + "1950 2.400000e+09 2.527000e+09 2.500000e+09 2.400000e+09 \n", + "1951 NaN NaN NaN NaN \n", + "1952 NaN NaN NaN NaN \n", + "1953 NaN NaN NaN NaN \n", + "1954 NaN NaN NaN NaN \n", + "1955 NaN NaN NaN NaN \n", + "1956 NaN NaN NaN NaN \n", + "1957 NaN NaN NaN NaN \n", + "1958 NaN NaN NaN NaN \n", + "1959 NaN NaN NaN NaN \n", + "1960 NaN NaN NaN NaN \n", + "1961 NaN NaN NaN NaN \n", + "1962 NaN NaN NaN NaN \n", + "1963 NaN NaN NaN NaN \n", + "1964 NaN NaN NaN NaN \n", + "1965 NaN NaN NaN NaN \n", + "1966 NaN NaN NaN NaN \n", + "1967 NaN NaN NaN NaN \n", + "1968 NaN NaN NaN NaN \n", + "1969 NaN NaN NaN NaN \n", + "1970 NaN 3.637000e+09 NaN 3.600000e+09 \n", + "1971 NaN NaN NaN NaN \n", + "1972 NaN NaN NaN NaN \n", + "1973 NaN NaN NaN NaN \n", + "1974 NaN NaN NaN NaN \n", + "1975 NaN NaN 3.900000e+09 4.000000e+09 \n", + "1976 NaN NaN NaN NaN \n", + "1977 NaN NaN NaN NaN \n", + "1978 NaN NaN NaN NaN \n", + "1979 NaN NaN NaN NaN \n", + "... ... ... ... ... \n", + "1986 NaN NaN NaN NaN \n", + "1987 NaN NaN NaN NaN \n", + "1988 NaN NaN NaN NaN \n", + "1989 NaN NaN NaN NaN \n", + "1990 NaN NaN NaN NaN \n", + "1991 NaN NaN NaN NaN \n", + "1992 NaN NaN NaN NaN \n", + "1993 NaN NaN NaN NaN \n", + "1994 NaN NaN NaN NaN \n", + "1995 NaN NaN NaN NaN \n", + "1996 NaN NaN NaN NaN \n", + "1997 NaN NaN NaN NaN \n", + "1998 NaN NaN NaN NaN \n", + "1999 NaN NaN NaN NaN \n", + "2000 NaN NaN 5.750000e+09 NaN \n", + "2001 NaN NaN NaN NaN \n", + "2002 NaN NaN NaN NaN \n", + "2003 NaN NaN NaN NaN \n", + "2004 NaN NaN NaN NaN \n", + "2005 NaN NaN NaN NaN \n", + "2006 NaN NaN NaN NaN \n", + "2007 NaN NaN NaN NaN \n", + "2008 NaN NaN NaN NaN \n", + "2009 NaN NaN NaN NaN \n", + "2010 NaN NaN NaN NaN \n", + "2011 NaN NaN NaN NaN \n", + "2012 NaN NaN NaN NaN \n", + "2013 NaN NaN NaN NaN \n", + "2014 NaN NaN NaN NaN \n", + "2015 NaN NaN NaN NaN \n", + "\n", + " durand clark \n", + "Year \n", + "1950 NaN 2.486000e+09 \n", + "1951 NaN NaN \n", + "1952 NaN NaN \n", + "1953 NaN NaN \n", + "1954 NaN NaN \n", + "1955 NaN NaN \n", + "1956 NaN NaN \n", + "1957 NaN NaN \n", + "1958 NaN NaN \n", + "1959 NaN NaN \n", + "1960 NaN NaN \n", + "1961 NaN NaN \n", + "1962 NaN 3.036000e+09 \n", + "1963 NaN NaN \n", + "1964 NaN NaN \n", + "1965 NaN NaN \n", + "1966 NaN 3.288000e+09 \n", + "1967 NaN NaN \n", + "1968 NaN NaN \n", + "1969 NaN NaN \n", + "1970 3,600,000,000– 3,700,000,000 3.632000e+09 \n", + "1971 NaN NaN \n", + "1972 NaN NaN \n", + "1973 NaN 3.860000e+09 \n", + "1974 NaN NaN \n", + "1975 NaN NaN \n", + "1976 NaN NaN \n", + "1977 NaN NaN \n", + "1978 NaN NaN \n", + "1979 NaN NaN \n", + "... ... ... \n", + "1986 NaN NaN \n", + "1987 NaN NaN \n", + "1988 NaN NaN \n", + "1989 NaN NaN \n", + "1990 NaN NaN \n", + "1991 NaN NaN \n", + "1992 NaN NaN \n", + "1993 NaN NaN \n", + "1994 NaN NaN \n", + "1995 NaN NaN \n", + "1996 NaN NaN \n", + "1997 NaN NaN \n", + "1998 NaN NaN \n", + "1999 NaN NaN \n", + "2000 NaN NaN \n", + "2001 NaN NaN \n", + "2002 NaN NaN \n", + "2003 NaN NaN \n", + "2004 NaN NaN \n", + "2005 NaN NaN \n", + "2006 NaN NaN \n", + "2007 NaN NaN \n", + "2008 NaN NaN \n", + "2009 NaN NaN \n", + "2010 NaN NaN \n", + "2011 NaN NaN \n", + "2012 NaN NaN \n", + "2013 NaN NaN \n", + "2014 NaN NaN \n", + "2015 NaN NaN \n", + "\n", + "[66 rows x 11 columns]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "table2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use dot notatio to select a column from a DataFrame. The result is a Series." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Year\n", + "1950 2557628654\n", + "1951 2594939877\n", + "1952 2636772306\n", + "1953 2682053389\n", + "1954 2730228104\n", + "1955 2782098943\n", + "1956 2835299673\n", + "1957 2891349717\n", + "1958 2948137248\n", + "1959 3000716593\n", + "1960 3043001508\n", + "1961 3083966929\n", + "1962 3140093217\n", + "1963 3209827882\n", + "1964 3281201306\n", + "1965 3350425793\n", + "1966 3420677923\n", + "1967 3490333715\n", + "1968 3562313822\n", + "1969 3637159050\n", + "1970 3712697742\n", + "1971 3790326948\n", + "1972 3866568653\n", + "1973 3942096442\n", + "1974 4016608813\n", + "1975 4089083233\n", + "1976 4160185010\n", + "1977 4232084578\n", + "1978 4304105753\n", + "1979 4379013942\n", + " ... \n", + "1986 4940571232\n", + "1987 5027200492\n", + "1988 5114557167\n", + "1989 5201440110\n", + "1990 5288955934\n", + "1991 5371585922\n", + "1992 5456136278\n", + "1993 5538268316\n", + "1994 5618682132\n", + "1995 5699202985\n", + "1996 5779440593\n", + "1997 5857972543\n", + "1998 5935213248\n", + "1999 6012074922\n", + "2000 6088571383\n", + "2001 6165219247\n", + "2002 6242016348\n", + "2003 6318590956\n", + "2004 6395699509\n", + "2005 6473044732\n", + "2006 6551263534\n", + "2007 6629913759\n", + "2008 6709049780\n", + "2009 6788214394\n", + "2010 6866332358\n", + "2011 6944055583\n", + "2012 7022349283\n", + "2013 7101027895\n", + "2014 7178722893\n", + "2015 7256490011\n", + "Name: census, Length: 66, dtype: int64" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "census = table2.census\n", + "census" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A Series object has two parts, `values` and `index`.\n", + "\n", + "The `values` part is an array." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([2557628654, 2594939877, 2636772306, 2682053389, 2730228104,\n", + " 2782098943, 2835299673, 2891349717, 2948137248, 3000716593,\n", + " 3043001508, 3083966929, 3140093217, 3209827882, 3281201306,\n", + " 3350425793, 3420677923, 3490333715, 3562313822, 3637159050,\n", + " 3712697742, 3790326948, 3866568653, 3942096442, 4016608813,\n", + " 4089083233, 4160185010, 4232084578, 4304105753, 4379013942,\n", + " 4451362735, 4534410125, 4614566561, 4695736743, 4774569391,\n", + " 4856462699, 4940571232, 5027200492, 5114557167, 5201440110,\n", + " 5288955934, 5371585922, 5456136278, 5538268316, 5618682132,\n", + " 5699202985, 5779440593, 5857972543, 5935213248, 6012074922,\n", + " 6088571383, 6165219247, 6242016348, 6318590956, 6395699509,\n", + " 6473044732, 6551263534, 6629913759, 6709049780, 6788214394,\n", + " 6866332358, 6944055583, 7022349283, 7101027895, 7178722893,\n", + " 7256490011], dtype=int64)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "census.values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `index` part is yet another kind of object, an `Int64Index`." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Int64Index([1950, 1951, 1952, 1953, 1954, 1955, 1956, 1957, 1958, 1959, 1960,\n", + " 1961, 1962, 1963, 1964, 1965, 1966, 1967, 1968, 1969, 1970, 1971,\n", + " 1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1980, 1981, 1982,\n", + " 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993,\n", + " 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,\n", + " 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015],\n", + " dtype='int64', name='Year')" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "census.index" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you ever wonder what kind of object a variable refers to, you can use the `type` function.\n", + "\n", + "The result indicates what type the object is, and the module where that type is defined.\n", + "\n", + "DataFrame, Series, and Int64Index are defined by Pandas.\n", + "\n", + "array is defined by NumPy." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pandas.core.frame.DataFrame" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(table2)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pandas.core.series.Series" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(census)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pandas.core.indexes.numeric.Int64Index" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(census.index)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "numpy.ndarray" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(census.values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This function plots the estimates generated by the US Censis and UN DESA, and labels the axes.\n", + "\n", + "`1e9` is scientific notation for $1 \\cdot 10^9$ or 1 billion." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def plot_estimates(table):\n", + " \"\"\"Plot world population estimates.\n", + " \n", + " table: DataFrame with columns 'un' and 'census'\n", + " \"\"\"\n", + " un = table.un / 1e9\n", + " census = table.census / 1e9\n", + " \n", + " plot(census, ':', color='darkblue', label='US Census')\n", + " plot(un, '--', color='green', label='UN DESA')\n", + " \n", + " decorate(xlabel='Year',\n", + " ylabel='World population (billion)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can plot the estimates." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving figure to file chap03-fig02.pdf\n" + ] + } + ], + "source": [ + "newfig()\n", + "plot_estimates(table2)\n", + "plot(results, '--', color='gray', label='model')\n", + "decorate(xlabel='Year', ylabel='World population (billion)')\n", + "savefig('chap03-fig02.pdf')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The model fits the data pretty well after 1990, but not so well before." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Try fitting the model using data from 1965 to the present, and see if that does a better job.\n", + "\n", + "Hint: Copy the code from above and make a few changes.\n", + "\n", + "Make sure your model starts in 1950, even though the estimated annual growth is based on later data. You might have to shift the first value in the series up or down to match the data." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "run_simulation1(system)\n", + "plot_results(system, title='Constant growth model')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`plot_results` uses `decorate`, which takes parameters that specify the title of the figure, labels for the $x$ and $y$ axis, and limits for the axes. To read the documentation of `decorate`, run the cells below." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on function decorate in module modsim:\n", + "\n", + "decorate(**kwargs)\n", + " Decorate the current axes.\n", + " \n", + " Call decorate with keyword arguments like\n", + " \n", + " decorate(title='Title',\n", + " xlabel='x',\n", + " ylabel='y')\n", + " \n", + " The keyword arguments can be any of the axis properties\n", + " defined by Matplotlib. To see the list, run plt.getp(plt.gca())\n", + " \n", + " In addition, you can use `legend=False` to suppress the legend.\n", + " \n", + " And you can use `loc` to indicate the location of the legend\n", + " (the default value is 'best')\n", + "\n" + ] + } + ], + "source": [ + "help(decorate)" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " adjustable = box\n", + " agg_filter = None\n", + " alpha = None\n", + " anchor = C\n", + " animated = False\n", + " aspect = auto\n", + " autoscale_on = True\n", + " autoscalex_on = True\n", + " autoscaley_on = True\n", + " axes = Axes(0.125,0.11;0.775x0.77)\n", + " axes_locator = None\n", + " axis_bgcolor = (1.0, 1.0, 1.0, 1.0)\n", + " axisbelow = True\n", + " children = [\n", + " label = \n", + " legend = Legend\n", + " legend_handles_labels = ([\n", + " navigate = True\n", + " navigate_mode = None\n", + " path_effects = []\n", + " picker = None\n", + " position = Bbox(x0=0.125, y0=0.10999999999999999, x1=0.9, y1=...\n", + " rasterization_zorder = None\n", + " rasterized = None\n", + " renderer_cache = \n", + " xlabel = Year\n", + " xlim = (1948.6800000000001, 2017.3199999999999)\n", + " xmajorticklabels = \n", + " xminorticklabels = \n", + " xscale = linear\n", + " xticklabels = \n", + " xticklines = \n", + " xticks = [ 1940. 1950. 1960. 1970. 1980. 1990.]...\n", + " yaxis = YAxis(80.000000,52.800000)\n", + " yaxis_transform = BlendedGenericTransform(BboxTransformTo(Transforme...\n", + " ybound = (2.4286625399999999, 7.4459584599999999)\n", + " ygridlines = \n", + " ylabel = World population (billion)\n", + " ylim = (2.4286625399999999, 7.4459584599999999)\n", + " ymajorticklabels = \n", + " yminorticklabels = \n", + " yscale = linear\n", + " yticklabels = \n", + " yticklines = \n", + " yticks = [ 2. 3. 4. 5. 6. 7.]...\n", + " zorder = 0\n" + ] + } + ], + "source": [ + "plt.getp(plt.gca())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** The constant growth model doesn't make a lot of sense, because it seems like the number of deaths and births should depend on the size of the population. As a small improvement, let's write a version of `run_simulation1` where the number of deaths is proportional to the size of the population, but the number of births is constant. This model doesn't make a lot of sense, either, but it's a good exercise.\n", + "\n", + "Write a function called `run_simulation1b` that implements a model where the number of births is constant, but the number of deaths is proportional to the current size of the population. Set the death rate to `0.01`, which means that 1% of the population dies each year; then choose the number of annual births to make the model fit the data as well as you can.\n", + "\n", + "Hint: It probably won't fit very well." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "\n", + "def run_simulation1b(system):\n", + "\n", + " results = TimeSeries()\n", + " results[system.t0] = system.p0\n", + " \n", + " for t in linrange(system.t0, system.t_end):\n", + " results[t+1] = results[t] - (results[t]*annual_death_rate) + annual_births\n", + " \n", + " system.results = results" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving figure to file chap03-fig03.pdf\n" + ] + } + ], + "source": [ + "run_simulation2(system)\n", + "plot_results(system, title='Proportional model')\n", + "savefig('chap03-fig03.pdf')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The model fits the data pretty well for the first 20 years, but not so well after that." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** In this implementation, we compute the number of deaths and births separately, but since they are both proportional to the current population, we can combine them.\n", + "\n", + "Write a function called `run_simulation2b` that implements a model with a single parameter, `alpha`, that represents the net growth rate, which is the difference between the birth and death rates. For example, if `alpha=0.01`, the population should grow by 1% per year.\n", + "\n", + "Choose the value of `alpha` that fits the data best." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def run_simulation2b(alpha, system = system):\n", + " alpha = system.birth_rate - system.death_rate\n", + " results = TimeSeries()\n", + " results[system.t0] = system.p0\n", + " for t in linrange(system.t0, system.t_end):\n", + " results[t+1] = results[t] + results[t]*alpha\n", + " system.results = results" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_results(system, title='Proportional model, factored')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** When you run `run_simulation`, it runs `update_func1` once for each year between `t0` and `t_end`. To see that for yourself, add a print statement at the beginning of `update_func1` that prints the values of `t` and `pop`, then run `run_simulation` again." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Combining birth and death" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since births and deaths get added up, we don't have to compute them separately. We can combine the birth and death rates into a single net growth rate." + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def update_func1b(pop, t, system):\n", + " \"\"\"Compute the population next year.\n", + " \n", + " pop: current population\n", + " t: current year\n", + " system: system object containing parameters of the model\n", + " \n", + " returns: population next year\n", + " \"\"\"\n", + " net_growth = system.alpha * pop\n", + " return pop + net_growth" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's how it works:" + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "run_simulation(system, update_func1c)\n", + "plot_results(system, title='Proportional model, combined birth and death')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Quadratic growth" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's the implementation of the quadratic growth model." + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def update_func2(pop, t, system):\n", + " \"\"\"Compute the population next year.\n", + " \n", + " pop: current population\n", + " t: current year\n", + " system: system object containing parameters of the model\n", + " \n", + " returns: population next year\n", + " \"\"\"\n", + " net_growth = system.alpha * pop + system.beta * pop**2\n", + " return pop + net_growth" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And here are the results. Can you find values for the parameters that make the model fit better?" + ] + }, + { + "cell_type": "code", + "execution_count": 102, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "run_simulation(system, update_func2)\n", + "plot_results(system, title='Proportional model, combined birth and death')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using `sns.set` to reset the plot style." + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "sns.set(style='white', font_scale=1.5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the book we found that the net growth is 0 when the population is $-\\alpha/\\beta$:" + ] + }, + { + "cell_type": "code", + "execution_count": 106, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "13.88888888888889" + ] + }, + "execution_count": 106, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "-system.alpha / system.beta" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is the equilibrium the population tends toward." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** In the book, I presented a different way to parameterize the quadratic model:\n", + "\n", + "$ \\Delta p = r p (1 - p / K) $\n", + "\n", + "where $r=\\alpha$ and $K=\\alpha/\\beta$. Write a version of `update_func2` that implements this version of the model. Test it by computing system variables `r` and `K` equivalent to `alpha` and `beta`, and confirm that you get the same results. " + ] + }, + { + "cell_type": "code", + "execution_count": 126, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def update_func2(pop, t, system): \n", + " net_growth = (system.r*pop) * (1-(pop/system.K))\n", + " return pop + net_growth" + ] + }, + { + "cell_type": "code", + "execution_count": 127, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(0.025, 13.88888888888889)" + ] + }, + "execution_count": 127, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "system.r = system.alpha\n", + "system.K = -system.alpha/system.beta\n", + "\n", + "system.r, system.K\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 128, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "newfig()\n", + "plot_prehistory(table1)\n", + "decorate(xlabel='Year', \n", + " ylabel='World population (millions)',\n", + " title='Prehistorical population estimates')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use `xlim` to zoom in on everything after Year 0." + ] + }, + { + "cell_type": "code", + "execution_count": 139, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "newfig()\n", + "plot_prehistory(table1)\n", + "plot(system1.results,label = 'model')\n", + "decorate(xlim=[0, 2000], xlabel='Year', \n", + " ylabel='World population (millions)',\n", + " title='Prehistorical population estimates')\n", + "legend(loc = 'best')" + ] + }, + { + "cell_type": "code", + "execution_count": 233, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "newfig()\n", + "plot_estimates(table2)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "### Running the quadratic model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's the update function for the quadratic growth model with parameters `alpha` and `beta`." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def update_func2(pop, t, system):\n", + " \"\"\"Update population based on a quadratic model.\n", + " \n", + " pop: current population in billions\n", + " t: what year it is\n", + " system: system object with model parameters\n", + " \"\"\"\n", + " net_growth = system.alpha * pop + system.beta * pop**2\n", + " return pop + net_growth" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Select the estimates generated by the U.S. Census, and convert to billions." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "census = table2.census / 1e9" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Extract the starting time and population." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "t0 = census.index[0]\n", + "p0 = census[t0]\n", + "t_end = census.index[-1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Initialize the system object." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
t01950.000000
t_end2015.000000
p02.557629
alpha0.025000
beta-0.001800
\n", + "
" + ], + "text/plain": [ + "t0 1950.000000\n", + "t_end 2015.000000\n", + "p0 2.557629\n", + "alpha 0.025000\n", + "beta -0.001800\n", + "dtype: float64" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "system = System(t0=t0, \n", + " t_end=t_end,\n", + " p0=p0,\n", + " alpha=0.025,\n", + " beta=-0.0018)\n", + "\n", + "system" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run the model and plot results." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving figure to file chap04-fig01.pdf\n" + ] + } + ], + "source": [ + "\n", + "system.t_end = 2250\n", + "run_simulation(system, update_func2)\n", + "plot_results(system)\n", + "decorate(title='World population projection')\n", + "savefig('chap04-fig01.pdf')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The population in the model converges on the equilibrium population, `-alpha/beta`" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "13.856665141368708" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "system.results[system.t_end]" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "13.888888888888889" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "-system.alpha / system.beta" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** What happens if we start with an initial population above the carrying capacity, like 20 billion? The the model with initial populations between 1 and 20 billion, and plot the results on the same axes." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving figure to file chap04-fig02.pdf\n" + ] + } + ], + "source": [ + "plot_results(system)\n", + "plot_projections(table3)\n", + "decorate(title='World population projections')\n", + "savefig('chap04-fig02.pdf')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "People who know what they are doing expect the growth rate to decline more sharply than our model projects." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Suppose there are two banks across the street from each other, The First Geometric Bank (FGB) and Exponential Savings and Loan (ESL). They offer the same interest rate on checking accounts, 3%, but at FGB, they compute and pay interest at the end of each year, and at ESL they compound interest continuously.\n", + "\n", + "If you deposit $p_0$ dollars at FGB at the beginning of Year 0, the balanace of your account at the end of Year $n$ is\n", + "\n", + "$ x_n = p_0 (1 + \\alpha)^n $\n", + "\n", + "where $\\alpha = 0.03$. At ESL, your balance at any time $t$ would be\n", + "\n", + "$ x(t) = p_0 \\exp(\\alpha t) $\n", + "\n", + "If you deposit \\$1000 at each back at the beginning of Year 0, how much would you have in each account after 10 years?\n", + "\n", + "Is there an interest rate FGB could pay so that your balance at the end of each year would be the same at both banks? What is it?\n", + "\n", + "Hint: `modsim` provides a function called `exp`, which is a wrapper for the NumPy function `exp`." + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "P = 1000\n", + "alpha = .03\n", + "n = 10" + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "yearly = P * (1 + alpha)**n" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "metadata": {}, + "outputs": [], + "source": [ + "cont = P * exp(alpha*n)" + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1343.9163793441223" + ] + }, + "execution_count": 87, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "yearly" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1349.8588075760031" + ] + }, + "execution_count": 88, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cont" + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "metadata": {}, + "outputs": [], + "source": [ + "alpha2 = exp(alpha) - 1" + ] + }, + { + "cell_type": "code", + "execution_count": 110, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving figure to file chap05-fig01.pdf\n" + ] + } + ], + "source": [ + "plot_results(system.S, system.I, system.R)\n", + "savefig('chap05-fig01.pdf')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using a DataFrame" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Instead of making three `TimeSeries` objects, we can use one `DataFrame`.\n", + "\n", + "We have to use `loc` to indicate which row we want to assign the results to. But then Pandas does the right thing, matching up the state variables with the columns of the `DataFrame`." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def run_simulation(system, update_func):\n", + " \"\"\"Runs a simulation of the system.\n", + " \n", + " Add a DataFrame to the System: results\n", + " \n", + " system: System object\n", + " update_func: function that updates state\n", + " \"\"\"\n", + " frame = DataFrame(columns=system.init.index)\n", + " frame.loc[system.t0] = system.init\n", + " \n", + " for t in linrange(system.t0, system.t_end):\n", + " frame.loc[t+1] = update_func(frame.loc[t], system)\n", + " \n", + " system.results = frame" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's how we run it, and what the result looks like." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
SIR
00.9888890.0111110.000000
10.9852260.0119960.002778
20.9812870.0129360.005777
30.9770550.0139340.009011
40.9725170.0149880.012494
\n", + "
" + ], + "text/plain": [ + " S I R\n", + "0 0.988889 0.011111 0.000000\n", + "1 0.985226 0.011996 0.002778\n", + "2 0.981287 0.012936 0.005777\n", + "3 0.977055 0.013934 0.009011\n", + "4 0.972517 0.014988 0.012494" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tc = 3 # time between contacts in days \n", + "tr = 4 # recovery time in days\n", + "\n", + "beta = 1 / tc # contact rate in per day\n", + "gamma = 1 / tr # recovery rate in per day\n", + "\n", + "sir = make_system(beta, gamma)\n", + "run_simulation(sir, update1)\n", + "sir.results.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can extract the results and plot them." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [], + "source": [ + "frame = sir.results\n", + "plot_results(frame.S, frame.I, frame.R)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise** Suppose the time between contacts is 4 days and the recovery time is 5 days. Simulate this scenario for 14 days and plot the results." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving figure to file chap05-fig03.pdf\n" + ] + } + ], + "source": [ + "newfig()\n", + "plot(infected_sweep)\n", + "\n", + "decorate(xlabel='Fraction immunized',\n", + " ylabel='Total fraction infected',\n", + " title='Fraction infected vs. immunization rate',\n", + " legend=False)\n", + "\n", + "savefig('chap05-fig03.pdf')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If 40% of the population is immunized, less than 4% of the population gets sick." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Logistic function" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To model the effect of a hand-washing campaign, I'll use a [generalized logistic function](https://en.wikipedia.org/wiki/Generalised_logistic_function), which is a convenient function for modeling curves that have a generally sigmoid shape. The parameters of the GLF correspond to various features of the curve in a way that makes it easy to find a function that has the shape you want, based on data or background information about the scenario." + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def logistic(x, A=0, B=1, C=1, M=0, K=1, Q=1, nu=1):\n", + " \"\"\"Computes the generalize logistic function.\n", + " \n", + " A: controls the lower bound\n", + " B: controls the steepness of the transition \n", + " C: not all that useful, AFAIK\n", + " M: controls the location of the transition\n", + " K: controls the upper bound\n", + " Q: shift the transition left or right\n", + " nu: affects the symmetry of the transition\n", + " \n", + " returns: float or array\n", + " \"\"\"\n", + " exponent = -B * (x - M)\n", + " denom = C + Q * exp(exponent)\n", + " return A + (K-A) / denom ** (1/nu)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following array represents the range of possible spending." + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0., 60., 120., 180., 240., 300., 360., 420.,\n", + " 480., 540., 600., 660., 720., 780., 840., 900.,\n", + " 960., 1020., 1080., 1140., 1200.])" + ] + }, + "execution_count": 84, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "spending = linspace(0, 1200, 21)\n", + "spending" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`compute_factor` computes the reduction in `beta` for a given level of campaign spending.\n", + "\n", + "`M` is chosen so the transition happens around \\$500.\n", + "\n", + "`K` is the maximum reduction in `beta`, 20%.\n", + "\n", + "`B` is chosen by trial and error to yield a curve that seems feasible." + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def compute_factor(spending):\n", + " \"\"\"Reduction factor as a function of spending.\n", + " \n", + " spending: dollars from 0 to 1200\n", + " \n", + " returns: fractional reduction in beta\n", + " \"\"\"\n", + " return logistic(spending, M=500, K=0.2, B=0.01)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's what it looks like." + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving figure to file chap05-fig05.pdf\n" + ] + } + ], + "source": [ + "newfig()\n", + "plot(infected_sweep)\n", + "\n", + "decorate(xlabel='Hand-washing campaign spending (USD)',\n", + " ylabel='Total fraction infected',\n", + " title='Effect of hand washing on total infections',\n", + " legend=False)\n", + "\n", + "savefig('chap05-fig05.pdf')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's put it all together to make some public health spending decisions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Optimization" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Suppose we have \\$1200 to spend on any combination of vaccines and a hand-washing campaign." + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "12" + ] + }, + "execution_count": 96, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "num_students = 90\n", + "budget = 1200\n", + "price_per_dose = 100\n", + "max_doses = int(budget / price_per_dose)\n", + "dose_array = linrange(max_doses)\n", + "max_doses" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can sweep through a range of doses from, 0 to `max_doses`, model the effects of immunization and the hand-washing campaign, and run simulations.\n", + "\n", + "For each scenario, we compute the fraction of students who get sick." + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.0 0.988888888889 0.266727403413 0.187595503995\n", + "1.0 0.977777777778 0.26683150821 0.174580718826\n", + "2.0 0.966666666667 0.267112856728 0.162909838349\n", + "3.0 0.955555555556 0.267865747331 0.153508349478\n", + "4.0 0.944444444444 0.269828391545 0.148565092315\n", + "5.0 0.933333333333 0.274613528135 0.152945950611\n", + "6.0 0.922222222222 0.284596094758 0.174964415024\n", + "7.0 0.911111111111 0.3 0.217343161684\n", + "8.0 0.9 0.315403905242 0.259071044488\n", + "9.0 0.888888888889 0.325386471865 0.278402884103\n", + "10.0 0.877777777778 0.330171608455 0.277914534623\n", + "11.0 0.866666666667 0.332134252669 0.267357496693\n", + "12.0 0.855555555556 0.332887143272 0.252796945636\n" + ] + } + ], + "source": [ + "for doses in dose_array:\n", + " fraction = doses / num_students\n", + " spending = budget - doses * price_per_dose\n", + " \n", + " system = make_system(beta, gamma)\n", + " add_immunization(system, fraction)\n", + " add_hand_washing(system, spending)\n", + " \n", + " run_simulation(system, update1)\n", + " print(doses, system.init.S, system.beta, calc_total_infected(system))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following function wraps that loop and stores the results in a `Sweep` object." + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def sweep_doses(dose_array):\n", + " \"\"\"Runs simulations with different doses and campaign spending.\n", + " \n", + " dose_array: range of values for number of vaccinations\n", + " \n", + " return: Sweep object with total number of infections \n", + " \"\"\"\n", + " sweep = SweepSeries()\n", + " for doses in dose_array:\n", + " fraction = doses / num_students\n", + " spending = budget - doses * price_per_dose\n", + " \n", + " system = make_system(beta, gamma)\n", + " add_immunization(system, fraction)\n", + " add_hand_washing(system, spending)\n", + " \n", + " run_simulation(system, update1)\n", + " sweep[doses] = calc_total_infected(system)\n", + "\n", + " return sweep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can compute the number of infected students for each possible allocation of the budget." + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "infected_sweep = sweep_doses(dose_array)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And plot the results." + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "frame = system.results\n", + "plot_results(frame.S, frame.I, frame.R)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sweeping beta" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Make a range of values for `beta`, with constant `gamma`." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "beta_array = linspace(0.1, 0.9, 11)\n", + "gamma = 0.25" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run the simulation once for each value of `beta` and print total infections." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.1 0.00723090166498\n", + "0.18 0.0262722567457\n", + "0.26 0.160575485321\n", + "0.34 0.490862856866\n", + "0.42 0.689867847411\n", + "0.5 0.804506112463\n", + "0.58 0.873610307851\n", + "0.66 0.916554007142\n", + "0.74 0.943729262152\n", + "0.82 0.961060480958\n", + "0.9 0.972099315633\n" + ] + } + ], + "source": [ + "for beta in beta_array:\n", + " system = make_system(beta, gamma)\n", + " run_simulation(system, update1)\n", + " print(system.beta, calc_total_infected(system))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Wrap that loop in a function and return a `SweepSeries` object." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def sweep_beta(beta_array, gamma):\n", + " \"\"\"SweepSeriess a range of values for beta.\n", + " \n", + " beta_array: array of beta values\n", + " gamma: recovery rate\n", + " \n", + " returns: SweepSeries that maps from beta to total infected\n", + " \"\"\"\n", + " sweep = SweepSeries()\n", + " for beta in beta_array:\n", + " system = make_system(beta, gamma)\n", + " run_simulation(system, update1)\n", + " sweep[system.beta] = calc_total_infected(system)\n", + " return sweep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "SweepSeries `beta` and plot the results." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "infected_sweep = sweep_beta(beta_array, gamma)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving figure to file chap06-fig01.pdf\n" + ] + } + ], + "source": [ + "label = 'gamma = ' + str(gamma)\n", + "plot(infected_sweep, label=label)\n", + "decorate(xlabel='Contacts per day (beta)',\n", + " ylabel='Fraction infected')\n", + "\n", + "savefig('chap06-fig01.pdf')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sweeping gamma" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using the same array of values for `beta`" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0.1 , 0.18, 0.26, 0.34, 0.42, 0.5 , 0.58, 0.66, 0.74,\n", + " 0.82, 0.9 ])" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "beta_array = linspace(0.1, 0.9, 11)\n", + "beta_array" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And now an array of values for `gamma`" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0.1, 0.3, 0.5, 0.7])" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gamma_array = linspace(0.1, 0.7, 4)\n", + "gamma_array" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For each value of `gamma`, SweepSeries `beta` and plot the results." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving figure to file chap06-fig02.pdf\n" + ] + } + ], + "source": [ + "for gamma in gamma_array:\n", + " infected_sweep = sweep_beta(beta_array, gamma)\n", + " label = 'gamma = ' + str(gamma)\n", + " plot(infected_sweep, label=label)\n", + " \n", + "decorate(xlabel='Contacts per day (beta)',\n", + " ylabel='Fraction infected',\n", + " loc='upper left')\n", + "\n", + "savefig('chap06-fig02.pdf')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now wrap that loop in a function and store the results in a `SweepSeriesFrame`" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def sweep_parameters(beta_array, gamma_array):\n", + " \"\"\"SweepSeriess a range of values for beta and gamma.\n", + " \n", + " beta_array: array of infection rates\n", + " gamma_array: array of recovery rates\n", + " \n", + " returns: SweepSeriesFrame with one row for each beta\n", + " and one column for each gamma\n", + " \"\"\"\n", + " frame = SweepFrame(columns=gamma_array)\n", + " for gamma in gamma_array:\n", + " frame[gamma] = sweep_beta(beta_array, gamma)\n", + " return frame" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's what the results look like." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
0.10.30.50.7
0.100.0846930.0054440.0027360.001827
0.180.7086230.0159140.0061180.003783
0.260.9007800.0553800.0116390.006427
0.340.9568880.2678640.0221150.010191
0.420.9770450.5245630.0478160.015946
\n", + "
" + ], + "text/plain": [ + " 0.1 0.3 0.5 0.7\n", + "0.10 0.084693 0.005444 0.002736 0.001827\n", + "0.18 0.708623 0.015914 0.006118 0.003783\n", + "0.26 0.900780 0.055380 0.011639 0.006427\n", + "0.34 0.956888 0.267864 0.022115 0.010191\n", + "0.42 0.977045 0.524563 0.047816 0.015946" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "frame = sweep_parameters(beta_array, gamma_array)\n", + "frame.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And here's how we can plot the results." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "for gamma in gamma_array:\n", + " label = 'gamma = ' + str(gamma)\n", + " plot(frame[gamma], label=label)\n", + " \n", + "decorate(xlabel='Contacts per day (beta)',\n", + " ylabel='Fraction infected',\n", + " loc='upper left')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It's often useful to separate the code that generates results from the code that plots the results, so we can run the simulations once, save the results, and then use them for different analysis, visualization, etc." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Contact number" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After running the SweepSeriess, we have a `SweepSeriesFrame` with one row for each value of `beta` and one column for each value of `gamma`." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(11, 4)" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "frame.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following loop shows how we can loop through the columns and rows of the `SweepSeriesFrame`. With 11 rows and 4 columns, there are 44 elements.\n", + "\n", + "One implementation note: when we select a column from a `SweepSeriesFrame` we get a `Series` object, rather than a `SweepSeries` object, but they are almost the same." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.1 0.1 0.0846929424381\n", + "0.18 0.1 0.70862278537\n", + "0.26 0.1 0.900780251778\n", + "0.34 0.1 0.956887899544\n", + "0.42 0.1 0.977045257074\n", + "0.5 0.1 0.984595862826\n", + "0.58 0.1 0.987400345318\n", + "0.66 0.1 0.988404249064\n", + "0.74 0.1 0.988743421406\n", + "0.82 0.1 0.988849515052\n", + "0.9 0.1 0.988879570517\n", + "0.1 0.3 0.00544355912239\n", + "0.18 0.3 0.0159140691448\n", + "0.26 0.3 0.0553797621068\n", + "0.34 0.3 0.267864167733\n", + "0.42 0.3 0.524562935844\n", + "0.5 0.3 0.686050483916\n", + "0.58 0.3 0.788378556339\n", + "0.66 0.3 0.85506574641\n", + "0.74 0.3 0.89947913569\n", + "0.82 0.3 0.929469302619\n", + "0.9 0.3 0.949853310327\n", + "0.1 0.5 0.00273576554115\n", + "0.18 0.5 0.00611834135832\n", + "0.26 0.5 0.0116394693217\n", + "0.34 0.5 0.0221147665242\n", + "0.42 0.5 0.0478162266689\n", + "0.5 0.5 0.132438038458\n", + "0.58 0.5 0.303264192648\n", + "0.66 0.5 0.464110227319\n", + "0.74 0.5 0.588476972528\n", + "0.82 0.5 0.682749610978\n", + "0.9 0.5 0.754595298329\n", + "0.1 0.7 0.001826769347\n", + "0.18 0.7 0.00378256160842\n", + "0.26 0.7 0.00642667221076\n", + "0.34 0.7 0.0101905519335\n", + "0.42 0.7 0.0159458265615\n", + "0.5 0.7 0.0257079250464\n", + "0.58 0.7 0.0450077531168\n", + "0.66 0.7 0.0906940688294\n", + "0.74 0.7 0.189795211656\n", + "0.82 0.7 0.318343186735\n", + "0.9 0.7 0.436999374456\n" + ] + } + ], + "source": [ + "for gamma in frame.columns:\n", + " series = frame[gamma]\n", + " for beta in series.index:\n", + " frac_infected = series[beta]\n", + " print(beta, gamma, frac_infected)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can wrap that loop in a function and plot the results. For each element of the `SweepSeriesFrame`, we have `beta`, `gamma`, and `frac_infected`, and we plot `beta/gamma` on the x-axis and `frac_infected` on the y-axis." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def plot_sweep_frame(frame):\n", + " \"\"\"Plots the values from a parameter SweepSeries.\n", + " \n", + " For each (beta, gamma), computes the contact number,\n", + " beta/gamma\n", + " \n", + " frame: SweepFrame with one row per beta, one column per gamma\n", + " \"\"\"\n", + " for gamma in frame.columns:\n", + " series = frame[gamma]\n", + " for beta in series.index:\n", + " frac_infected = series[beta]\n", + " plot(beta/gamma, frac_infected, 'ro',\n", + " label='Simulation')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's what it looks like:" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving figure to file chap06-fig03.pdf\n" + ] + } + ], + "source": [ + "plot_sweep_frame(frame)\n", + "\n", + "decorate(xlabel='Contact number (beta/gamma)',\n", + " ylabel='Fraction infected',\n", + " legend=False)\n", + "\n", + "savefig('chap06-fig03.pdf')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It turns out that the ratio `beta/gamma`, called the \"contact number\" is sufficient to predict the total number of infections; we don't have to know `beta` and `gamma` separately.\n", + "\n", + "We can see that in the previous plot: when we plot the fraction infected versus the contact number, the results fall close to a curve.\n", + "\n", + "But if we didn't know about the contact number, we might have explored other possibilities, like the difference between `beta` and `gamma`, rather than their ratio." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Write a version of `plot_sweep_frame`, called `plot_sweep_frame_difference`, that plots the fraction infected versus the difference `beta-gamma`.\n", + "\n", + "What do the results look like, and what does that imply? " + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def plot_sweep_frame_difference(frame):\n", + " \"\"\"Plots the values from a parameter SweepSeries.\n", + " \n", + " For each (beta, gamma), computes the contact number,\n", + " beta/gamma\n", + " \n", + " frame: SweepFrame with one row per beta, one column per gamma\n", + " \"\"\"\n", + " for gamma in frame.columns:\n", + " series = frame[gamma]\n", + " for beta in series.index:\n", + " frac_infected = series[beta]\n", + " plot(beta-gamma, frac_infected, 'ro',\n", + " label='Simulation')" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot(coffee.results.temp, label='coffee')\n", + "decorate(xlabel='Time (minutes)',\n", + " ylabel='Temperature (C)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After running the simulation, we can extract the final temperature from the results." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def final_temp(system):\n", + " \"\"\"Final temperature.\n", + " \n", + " If system has no results, return initial temp.\n", + " \n", + " system: System object.\n", + " \n", + " returns: temperature (degC)\n", + " \"\"\" \n", + " if hasattr(system, 'results'):\n", + " return system.results.temp[system.t_end]\n", + " else:\n", + " return system.init.temp" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It will be convenient to wrap these steps in a function. `kwargs` is a collection of whatever keyword arguments are provided; they are passed along as arguments to `System`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def make_system(T_init=90, r=0.01, volume=300, t_end=30):\n", + " \"\"\"Runs a simulation with the given parameters.\n", + "\n", + " T_init: initial temperature in degC\n", + " r: heat transfer rate, in 1/min\n", + " volume: volume of liquid in mL\n", + " t_end: end time of simulation\n", + " \n", + " returns: System object\n", + " \"\"\"\n", + " init = State(temp=T_init)\n", + " \n", + " system = System(init=init,\n", + " volume=volume,\n", + " r=r,\n", + " T_env=22, \n", + " t0=0,\n", + " t_end=t_end,\n", + " dt=1)\n", + " return system" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's how we use it:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "72.299625390403094" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "coffee = make_system()\n", + "run_simulation(coffee, update)\n", + "final_temp(coffee)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Simulate the temperature of 50 mL of milk with a starting temperature of 5 degC, in a vessel with the same insulation, for 15 minutes, and plot the results." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "milk = make_system(T_init=5, r=0.01, volume=50, t_end=15)\n", + "run_simulation(milk, update)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving figure to file chap07-fig01.pdf\n" + ] + } + ], + "source": [ + "newfig()\n", + "plot(coffee.results.temp, label='coffee')\n", + "plot(milk.results.temp, '--', label='milk')\n", + "decorate(xlabel='Time (minutes)',\n", + " ylabel='Temperature (C)',\n", + " loc='center left')\n", + "\n", + "savefig('chap07-fig01.pdf')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's what happens when we mix them." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "63.039907187045898" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mix_last = mix(coffee, milk)\n", + "final_temp(mix_last)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And here's what we get if we add the milk immediately." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "coffee = make_system(T_init=90, r=r_coffee, volume=300)\n", + "milk = make_system(T_init=5, r=r_milk, volume=50)" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "61.428571428571438" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mix_first = mix(coffee, milk)\n", + "mix_first.t_end = 30\n", + "run_simulation(mix_first, update)\n", + "final_temp(mix_first)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following function takes `t_add`, which is the time when the milk is added, and returns the final temperature." + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def run_and_mix(t_add, t_total=30):\n", + " \"\"\"Simulates two liquids and them mixes them at t_add.\n", + " \n", + " t_add: time in minutes\n", + " t_total: total time to simulate, min\n", + " \n", + " returns: final temperature\n", + " \"\"\"\n", + " coffee = make_system(T_init=90, t_end=t_add, \n", + " r=r_coffee, volume=300)\n", + " run_simulation(coffee, update)\n", + "\n", + " milk = make_system(T_init=5, t_end=t_add, \n", + " r=r_milk, volume=50)\n", + " run_simulation(milk, update)\n", + " \n", + " mixture = mix(coffee, milk)\n", + " mixture.t_end = t_total - t_add\n", + " run_simulation(mixture, update)\n", + "\n", + " return final_temp(mixture)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can try it out with a few values." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "61.428571428571438" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_and_mix(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "62.722755204592659" + ] + }, + "execution_count": 61, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_and_mix(15)" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "63.039907187045898" + ] + }, + "execution_count": 62, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_and_mix(30)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And then sweep a range of values for `t_add`" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "sweep = SweepSeries()\n", + "for t_add in linrange(0, 30, 2):\n", + " temp = run_and_mix(t_add)\n", + " sweep[t_add] = temp" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's what the result looks like." + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot(data.glucose, 'bo', label='glucose')\n", + "decorate(xlabel='Time (min)',\n", + " ylabel='Concentration (mg/dL)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And the insulin time series." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\ProgramData\\Miniconda3\\lib\\site-packages\\matplotlib\\axes\\_axes.py:545: UserWarning: No labelled objects found. Use label='...' kwarg on individual plots.\n", + " warnings.warn(\"No labelled objects found. \"\n" + ] + } + ], + "source": [ + "plot(data.insulin, 'go', label='insulin')\n", + "decorate(xlabel='Time (min)',\n", + " ylabel='Concentration ($\\mu$U/mL)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the book, I put them in a single figure, using `subplot`" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving figure to file chap08-fig02.pdf\n" + ] + } + ], + "source": [ + "ts = linrange(0, 182, 2)\n", + "newfig()\n", + "plot(data.insulin, 'go', label='insulin data')\n", + "plot(ts, I(ts), color='green', label='interpolated')\n", + "\n", + "decorate(xlabel='Time (min)',\n", + " ylabel='Concentration ($\\mu$U/mL)')\n", + "\n", + "savefig('chap08-fig02.pdf')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** [Read the documentation](https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp1d.html) of `scipy.interpolate.interp1d`. Pass a keyword argument to `interpolate` to specify one of the other kinds of interpolation, and run the code again to see what it looks like. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The glucose minimal model\n", + "\n", + "I'll cheat by starting with parameters that fit the data roughly; then we'll see how to improve them." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "k1 = 0.03\n", + "k2 = 0.02\n", + "k3 = 1e-05\n", + "G0 = 290" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To estimate basal levels, we'll use the concentrations at `t=0`." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "Gb = data.glucose[0]\n", + "Ib = data.insulin[0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the initial conditions, `X(0)=0` and `G(0)=G0`, where `G0` is one of the parameters we'll choose." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "init = State(G=G0, X=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's the system object with all parameters and the interpolation object `I`." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "system = System(init=init, \n", + " k1=k1, k2=k2, k3=k3,\n", + " I=I, Gb=Gb, Ib=Ib,\n", + " t0=0, t_end=182, dt=2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And here's the update function. Using `unpack` to make the system variables accessible without using dot notation, which makes the translation of the differential equations more readable and checkable." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def update_func(state, t, system):\n", + " \"\"\"Updates the glucose minimal model.\n", + " \n", + " state: State object\n", + " t: time in min\n", + " system: System object\n", + " \n", + " returns: State object\n", + " \"\"\"\n", + " G, X = state\n", + " unpack(system)\n", + " \n", + " dGdt = -k1 * (G - Gb) - X*G\n", + " dXdt = k3 * (I(t) - Ib) - k2 * X\n", + " \n", + " G += dGdt * dt\n", + " X += dXdt * dt\n", + "\n", + " return State(G=G, X=X)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Before running the simulation, it is always a good idea to test the update function using the initial conditions. In this case we can veryify that the results are at least qualitatively correct." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
G278.12
X0.00
\n", + "
" + ], + "text/plain": [ + "G 278.12\n", + "X 0.00\n", + "dtype: float64" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "update_func(init, 0, system)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now run simulation is pretty much the same as it always is." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def run_simulation(system, update_func):\n", + " \"\"\"Runs a simulation of the system.\n", + " \n", + " Adds a TimeFrame to `system` as `results`\n", + " \n", + " system: System object\n", + " update_func: function that updates state\n", + " \"\"\"\n", + " unpack(system)\n", + " \n", + " frame = TimeFrame(columns=init.index)\n", + " frame.loc[t0] = init\n", + " ts = linrange(t0, t_end-dt, dt)\n", + " \n", + " for t in ts:\n", + " frame.loc[t+dt] = update_func(frame.loc[t], t, system)\n", + " \n", + " system.results = frame" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And here's how we run it. `%time` is a Jupyter magic command that runs the function and reports its run time." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Wall time: 155 ms\n" + ] + } + ], + "source": [ + "%time run_simulation(system, update_func)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The results are in a `TimeFrame object` with one column per state variable." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
GX
0290.0000000.000000
2278.1200000.000000
4266.9528000.000300
6256.2954600.002668
8245.0701400.004041
10233.9051380.004680
12223.2016510.005252
14212.9848440.005722
16203.2882070.006093
18194.1334610.006330
20185.5478350.006457
22177.5389760.006578
24170.0708410.006695
26163.1092960.006807
28156.6220610.006855
30150.5974380.006901
32145.0030960.007005
34139.7914750.007105
36134.9376590.007200
38130.4181830.007292
40126.2109670.007221
42122.3356430.007152
44118.7656440.007086
46115.4766040.007022
48112.4461680.006961
50109.6538150.006763
52107.1114040.006572
54104.7967400.006390
56102.6897170.006214
58100.7721030.006045
.........
12486.3436630.001113
12686.4908330.000989
12886.6503860.000869
13086.8207670.000754
13287.0005560.000644
13487.1884560.000538
13687.3832820.000457
13887.5804580.000378
14087.7793330.000303
14287.9793170.000231
14488.1798730.000162
14688.3805150.000095
14888.5808050.000032
15088.780346-0.000030
15288.978780-0.000088
15489.175786-0.000145
15689.371078-0.000199
15889.564397-0.000251
16089.755515-0.000301
16289.944230-0.000349
16490.130362-0.000395
16690.313756-0.000439
16890.494274-0.000482
17090.671799-0.000522
17290.846229-0.000562
17491.017481-0.000599
17691.185484-0.000655
17891.353827-0.000709
18091.522119-0.000761
18291.690005-0.000810
\n", + "

92 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " G X\n", + "0 290.000000 0.000000\n", + "2 278.120000 0.000000\n", + "4 266.952800 0.000300\n", + "6 256.295460 0.002668\n", + "8 245.070140 0.004041\n", + "10 233.905138 0.004680\n", + "12 223.201651 0.005252\n", + "14 212.984844 0.005722\n", + "16 203.288207 0.006093\n", + "18 194.133461 0.006330\n", + "20 185.547835 0.006457\n", + "22 177.538976 0.006578\n", + "24 170.070841 0.006695\n", + "26 163.109296 0.006807\n", + "28 156.622061 0.006855\n", + "30 150.597438 0.006901\n", + "32 145.003096 0.007005\n", + "34 139.791475 0.007105\n", + "36 134.937659 0.007200\n", + "38 130.418183 0.007292\n", + "40 126.210967 0.007221\n", + "42 122.335643 0.007152\n", + "44 118.765644 0.007086\n", + "46 115.476604 0.007022\n", + "48 112.446168 0.006961\n", + "50 109.653815 0.006763\n", + "52 107.111404 0.006572\n", + "54 104.796740 0.006390\n", + "56 102.689717 0.006214\n", + "58 100.772103 0.006045\n", + ".. ... ...\n", + "124 86.343663 0.001113\n", + "126 86.490833 0.000989\n", + "128 86.650386 0.000869\n", + "130 86.820767 0.000754\n", + "132 87.000556 0.000644\n", + "134 87.188456 0.000538\n", + "136 87.383282 0.000457\n", + "138 87.580458 0.000378\n", + "140 87.779333 0.000303\n", + "142 87.979317 0.000231\n", + "144 88.179873 0.000162\n", + "146 88.380515 0.000095\n", + "148 88.580805 0.000032\n", + "150 88.780346 -0.000030\n", + "152 88.978780 -0.000088\n", + "154 89.175786 -0.000145\n", + "156 89.371078 -0.000199\n", + "158 89.564397 -0.000251\n", + "160 89.755515 -0.000301\n", + "162 89.944230 -0.000349\n", + "164 90.130362 -0.000395\n", + "166 90.313756 -0.000439\n", + "168 90.494274 -0.000482\n", + "170 90.671799 -0.000522\n", + "172 90.846229 -0.000562\n", + "174 91.017481 -0.000599\n", + "176 91.185484 -0.000655\n", + "178 91.353827 -0.000709\n", + "180 91.522119 -0.000761\n", + "182 91.690005 -0.000810\n", + "\n", + "[92 rows x 2 columns]" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "system.results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following plot shows the results of the simulation along with the actual glucose data." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving figure to file chap08-fig04.pdf\n" + ] + } + ], + "source": [ + "newfig()\n", + "plot(system.results.G, label='simulation')\n", + "plot(data.glucose, style='bo', label='glucose data')\n", + "\n", + "decorate(xlabel='Time (min)',\n", + " ylabel='Concentration (mg/dL)')\n", + "\n", + "savefig('chap08-fig04.pdf')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Since we don't expect the first few points to agree, it's probably better not to make them part of the optimization process. We can ignore them by leaving them out of the `Series` returned by `error_func`. Modify the last line of `error_func` to return `errors.loc[8:]`, which includes only the elements of the `Series` from `t=8` and up.\n", + "\n", + "Does that improve the quality of the fit? Does it change the best parameters by much?\n", + "\n", + "Note: You can read more about this use of `loc` [in the Pandas documentation](https://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-integer)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** How sensitive are the results to the starting guess for the parameters. If you try different values for the starting guess, do we get the same values for the best parameters?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Interpreting parameters\n", + "\n", + "Based on the parameters of the model, we can estimate glucose effectiveness and insulin sensitivity." + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def indices(G0, k1, k2, k3):\n", + " \"\"\"Compute glucose effectiveness and insulin sensitivity.\n", + " \n", + " G0: initial blood glucose\n", + " k1: rate parameter\n", + " k2: rate parameter\n", + " k3: rate parameter\n", + " data: DataFrame\n", + " \n", + " returns: State object containing S_G and S_I\n", + " \"\"\"\n", + " return State(S_G=k1, S_I=k3/k2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here are the results." + ] + }, + { + "cell_type": "code", + "execution_count": 109, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
S_G0.026799
S_I0.000881
\n", + "
" + ], + "text/plain": [ + "S_G 0.026799\n", + "S_I 0.000881\n", + "dtype: float64" + ] + }, + "execution_count": 109, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "indices(*best_params)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The insulin minimal model\n", + "\n", + "In addition to the glucose minimal mode, Pacini and Bergman present an insulin minimal model, in which the concentration of insulin, $I$, is governed by this differential equation:\n", + "\n", + "$ \\frac{dI}{dt} = -k I(t) + \\gamma (G(t) - G_T) t $" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Write a version of `make_system` that takes the parameters of this model, `I0`, `k`, `gamma`, and `G_T` as parameters, along with a `DataFrame` containing the measurements, and returns a `System` object suitable for use with `run_simulation` or `run_odeint`.\n", + "\n", + "Use it to make a `System` object with the following parameters:" + ] + }, + { + "cell_type": "code", + "execution_count": 110, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "I0 = 360\n", + "k = 0.25\n", + "gamma = 0.004\n", + "G_T = 80" + ] + }, + { + "cell_type": "code", + "execution_count": 111, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def make_system(I0, k, gamma, G_T, data):\n", + " \"\"\"Makes a System object with the given parameters.\n", + "\n", + " returns: System object\n", + " \"\"\"\n", + " init = State(I0=I0)\n", + " system = System(init, k=k, gamma = gamma, G_T = G_T,\n", + " G=interpolate(data.glucose),\n", + " ts=data.index)\n", + " return system" + ] + }, + { + "cell_type": "code", + "execution_count": 112, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
I0360
\n", + "
" + ], + "text/plain": [ + "I0 360\n", + "dtype: int64" + ] + }, + "execution_count": 112, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "make_system(I0, k, gamma, G_T, data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Write a slope function that takes state, t, system as parameters and returns the derivative of `I` with respect to time. Test your function with the initial condition $I(0)=360$." + ] + }, + { + "cell_type": "code", + "execution_count": 113, + "metadata": {}, + "outputs": [], + "source": [ + "def slope_func(state, t, system):\n", + "\n", + " I = state\n", + " unpack(system)\n", + " \n", + " dIdt = - k * I + gamma * (G(t) - G_T) * t\n", + "\n", + " \n", + " return dIdt" + ] + }, + { + "cell_type": "code", + "execution_count": 114, + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "'int' object is not callable", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0minit\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mState\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mI\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mI0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mslope_func\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minit\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0msystem\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32m\u001b[0m in \u001b[0;36mslope_func\u001b[1;34m(state, t, system)\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[0munpack\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0msystem\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 6\u001b[1;33m \u001b[0mdIdt\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m-\u001b[0m \u001b[0mk\u001b[0m \u001b[1;33m*\u001b[0m \u001b[0mI\u001b[0m \u001b[1;33m+\u001b[0m \u001b[0mgamma\u001b[0m \u001b[1;33m*\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0mG\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mt\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m-\u001b[0m \u001b[0mG_T\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m*\u001b[0m \u001b[0mt\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 7\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 8\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mTypeError\u001b[0m: 'int' object is not callable" + ] + } + ], + "source": [ + "init = State(I=I0)\n", + "slope_func(init, 0, system)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Run `run_odeint` with your `System` object and slope function, and plot the results, along with the measured insulin levels." + ] + }, + { + "cell_type": "code", + "execution_count": 118, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Before running scipy.integrate.odeint, I tried\n", + " running the slope function you provided with the\n", + " initial conditions in system and t=0, and I got\n", + " the following error:\n" + ] + }, + { + "ename": "TypeError", + "evalue": "'int' object is not callable", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mrun_odeint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0msystem\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mslope_func\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 2\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;31m#can't plot because error\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;31m# if I were to plot, I'd plot the timeframe 'results' from the system created by run_odeint\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;31m# in the same fig I'd plot data.insulin\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m~\\Documents\\GitHub\\ModSimPy\\code\\modsim.py\u001b[0m in \u001b[0;36mrun_odeint\u001b[1;34m(system, slope_func, **kwargs)\u001b[0m\n\u001b[0;32m 269\u001b[0m the following error:\"\"\"\n\u001b[0;32m 270\u001b[0m \u001b[0mlogger\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0merror\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 271\u001b[1;33m \u001b[1;32mraise\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0me\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 272\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 273\u001b[0m \u001b[1;31m# when odeint calls slope_func, it should pass `system` as\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m~\\Documents\\GitHub\\ModSimPy\\code\\modsim.py\u001b[0m in \u001b[0;36mrun_odeint\u001b[1;34m(system, slope_func, **kwargs)\u001b[0m\n\u001b[0;32m 262\u001b[0m \u001b[1;31m# try running the slope function with the initial conditions\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 263\u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 264\u001b[1;33m \u001b[0mslope_func\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minit\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mts\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0msystem\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 265\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mException\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 266\u001b[0m msg = \"\"\"Before running scipy.integrate.odeint, I tried\n", + "\u001b[1;32m\u001b[0m in \u001b[0;36mslope_func\u001b[1;34m(state, t, system)\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[0munpack\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0msystem\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 6\u001b[1;33m \u001b[0mdIdt\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m-\u001b[0m \u001b[0mk\u001b[0m \u001b[1;33m*\u001b[0m \u001b[0mI\u001b[0m \u001b[1;33m+\u001b[0m \u001b[0mgamma\u001b[0m \u001b[1;33m*\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0mG\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mt\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m-\u001b[0m \u001b[0mG_T\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m*\u001b[0m \u001b[0mt\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 7\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 8\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mTypeError\u001b[0m: 'int' object is not callable" + ] + } + ], + "source": [ + "run_odeint(system, slope_func)\n", + "\n", + "#can't plot because error\n", + "# if I were to plot, I'd plot the timeframe 'results' from the system created by run_odeint\n", + "# in the same fig I'd plot data.insulin" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Write an error function that takes a sequence of parameters as an argument, along with the `DataFrame` containing the measurements. It should make a `System` object with the given parameters, run it, and compute the difference between the results of the simulation and the measured values. Test your error function by calling it with the parameters from the previous exercise.\n", + "\n", + "Hint: As we did in a previous exercise, you might want to drop the errors for times prior to `t=8`." + ] + }, + { + "cell_type": "code", + "execution_count": 119, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def error_func(params, data):\n", + "\n", + " \n", + " print(params)\n", + " \n", + " # make a System with the given parameters\n", + " system = make_system(*params, data)\n", + " \n", + " # solve the ODE\n", + " run_odeint(system, slope_func)\n", + " \n", + " # compute the difference between the model\n", + " # results and actual data\n", + " error = system.results.I - data.ginsulin\n", + " return error.loc[8:]" + ] + }, + { + "cell_type": "code", + "execution_count": 120, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "#again, the error earlier prevents me from running the function" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Use `fit_leastsq` to find the parameters that best fit the data. Make a `System` object with those parameters, run it, and plot the results along with the measurements." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "ls = fit_leastsq(error_func, params, data)" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "system = make_system(*ls, data)" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "run_odeint(system, slope_func)" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Using the best parameters, estimate the sensitivity to glucose of the first and second phase pancreatic responsivity:\n", + "\n", + "$ \\phi_1 = \\frac{I_{max} - I_b}{k (G_0 - G_b)} $\n", + "\n", + "$ \\phi_2 = \\gamma \\times 10^4 $" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/code/chap08soln.ipynb b/code/chap08soln.ipynb index 24f4b3ed..424fdb21 100644 --- a/code/chap08soln.ipynb +++ b/code/chap08soln.ipynb @@ -261,9 +261,9 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAf8AAAFhCAYAAAB+naONAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3XlcVPX+P/DXgBCo7IwIVyCVwA1kVDTANMFQKqPyouWS\noIBkatB1gbxiIuhVycxdLmqKftW6aimKpVQaZkoaIlpZLjAsAsKgoiDb/P7gBzmxOMgsDPN6Ph4+\nkHPOnPM+npEX8zmf8/kIpFKpFERERKQ1dNRdABEREakWw5+IiEjLMPyJiIi0DMOfiIhIyzD8iYiI\ntEwndRegChUVFcjMzIRQKISurq66yyEiIlK6mpoaFBUVYcCAATAwMJBZpxXhn5mZicmTJ6u7DCIi\nIpXbs2cPhgwZIrNM5eH/xx9/4OOPP8Yvv/yChw8fwsHBAe+99x5Gjx6N9evXY+PGjdDT05N5zYwZ\nMxAWFgYAEIvFiI2NRUZGBqRSKQYOHIhFixbB1ta22WMKhUIAdf8A3bt3V97JERERtRO3b9/G5MmT\nGzLwcSoN//LyckyZMgV+fn6Ii4uDvr4+tm3bhrlz5+Lw4cMAADc3NyQmJjb5+qqqKgQHB8PFxQVJ\nSUno1KkTVqxYgaCgICQlJTX6paFefVN/9+7d0aNHD+WcHBERUTvU1O1ulXb4Ky8vx7x58xAeHo6u\nXbtCX18fU6ZMQU1NDa5du/bE16empiIrKwuRkZEwNzeHsbExFi5cCLFYjFOnTim01rQ0IDoaePfd\nuq9paQrdPRERkdqoNPzNzc3h7+8PQ0NDAIBEIsGmTZvQvXt3uLu7A6hrpggMDMSwYcPg5eWFlStX\noqKiAgCQnp4OOzs7mJmZNezT1NQUtra2uHTpksLqTEsDEhKA3Fygtrbua0ICfwEgIqKOQW0d/gYM\nGICqqio4Oztj+/btMDMzQ7du3WBnZ4ewsDD06dMH6enpCA8Px8OHD7F06VJIJBKYmJg02peZmRmK\ni4sVVltyctPLjx8H3NwUdhgiIiK1UNtz/pmZmTh79ixGjhyJSZMm4ebNm5g4cSK2bdsGZ2dn6Onp\nwc3NDSEhITh48CCqq6tb3J9AIFBYbfn5TS/Py1PYIYiIiNRGrYP8mJubY86cObCyssK+ffua3Mbe\n3h6VlZWQSCSwsLBAaWlpo20kEgksLS0VVpe1ddPLbWwUdggiIiK1UWn4p6SkwMvLC48ePZJZXllZ\nCV1dXWzevBnff/+9zLrr16+jc+fOsLS0hEgkglgslmniv3PnDrKzsxs9w9gWvr5NLx87VmGHICIi\nUhuVhr9IJEJ5eTmio6NRWlqKR48eYefOncjOzoaPjw9KS0sRFRWFy5cvo7q6GmlpaUhISEBgYCAE\nAgE8PT3h4OCA2NhYSCQSlJSUICYmBo6OjvDw8FBYnW5uQFAQ0KMHoKNT9zUoiPf7iYioY1Bphz9z\nc3Ps2rULK1euxKhRo6Cjo4NevXphw4YNcHV1Rb9+/WBgYICwsDAUFhZCKBQiKCgI06ZNA1D3rGJ8\nfDyio6Ph5eUFgUAADw8PxMfHK3zYXjc3hj0REXVMAqlUKlV3EcqWk5MDb29vpKSkcJAfIiLSCi1l\nH2f1IyIi0jIMfyIiIi3D8CciIq0UERGBt99+W91lqAXDn4iISMuobXhfIiLSDmlpdcOm5+fXDaLm\n68unqdSNn/yJiEhp1D1RWkZGBt544w24uLhg3LhxOH/+PFxdXXHw4EGZ7c6dOwcnJydkZWU1LPvx\nxx/h5OSEnJwcAMCDBw/w0Ucfwd3dHW5ubpg5cyays7MfO9c0vPXWWxgyZAiGDBmC9957D7m5uQ3r\njx49inHjxkEkEmHo0KGYPXs2CgoKGtYnJyfjzTffhEgkgru7OxYvXoyysjKl/Lsw/ImISGlamihN\n2aRSKcLDw2FjY4MzZ85g06ZN2LBhA8rLy59qf1FRUfjtt9/w5Zdf4vTp0zAxMUFwcDBqa2uRlZWF\ngIAA+Pj4IDU1FV9//TXKy8sxc+ZMSKVSFBQUYP78+Zg3bx4uXryIr7/+GgCwatUqAHW/aCxcuBCz\nZs3Czz//jP379yMzMxOxsbEK+/d4HMOfiIiURp0TpV2+fBk5OTl47733YGRkBFtbW4SEhDzVviQS\nCZKTkxESEgIrKysYGhpiwYIFeP/99/Ho0SPs27cPPXv2xPTp02FgYAALCwt88MEH+OOPP3D58mWU\nlZWhpqYGhoaGEAgEMDMzw/r16/Hxxx8DAPbs2YOXXnoJo0ePhq6uLuzs7DBnzhwcOXKkYVp7ReI9\nfyIiUhpr67qm/r9TxURp+f//Nw87O7uGZa6urk+1r5ycHNTU1MgMlmNpaYmXX34ZAJCVlQUHBweZ\n19R/n52djVdffRXvvPMOAgIC4OjoiOeffx6+vr4YOHAgAODGjRvIysrCN998I7OP2tpaFBQUwN7e\n/qnqbg4/+RMRkdKoc6K02tpaAICenl7DstZM/15TU9Pw9/oh5Ov3+XePHj3C3wfMrd+2/piLFi3C\nd999h6lTpyI/Px+TJ0/GJ598AgAwMDDApEmTcPnyZZk/V69eVXjwAwx/IiJSInVOlCYUCgEAYrG4\nYVl6enqT2xoYGACATH+Axzvz2draolOnTrhx40bDspKSEmzbtg2lpaXo2bMnrl27JrPPP/74AwDQ\ns2dP1NbWorS0FFZWVhg/fjw+/fRTLFmyBImJiQCAZ599Fr/++qvM6+/du9fkNPaKwPAnIiKlcnMD\nFi8GNm+u+6qqx/xcXV0hFAqxefNmPHjwADk5Odi+fXuT29ra2kJPTw/Hjh1DTU0N/vzzT5knAoyM\njPDqq69iy5YtEIvFqKiowKefforPP/8cRkZG+Oc//4ns7Gz897//RWVlJQoKChAXFwcXFxf069cP\nSUlJePXVV5GRkQGpVIoHDx4gMzMTvXr1AgC88847uHDhAvbs2YOKigoUFRVh3rx5CA8PV8q/DcOf\niIg6pE6dOmH16tXIzMyEu7s7wsLCMHv2bACAjo5s/JmbmyMyMhKHDh3C4MGDsWzZMsydO1dmm6VL\nl8LV1RVvvvkmhg8fjry8PGzduhW6urro06cPNm3ahBMnTsDd3R0TJkyAra0t4uPjAQDjxo3D5MmT\nERYWhoEDB8Lb2xt37tzBmjVrANRNef/xxx9j3759cHNzw+uvvw4zM7OGDoGKxln9iIiow6qtrUVN\nTU3Dff/6PNi5cyeef/55NVenXJzVj4iItJKfnx/mz5+PsrIyPHjwABs2bEC3bt3g4uKi7tLUiuFP\nREQd1po1a1BaWoqRI0di1KhRyM/Px9atW9G5c2d1l6ZWfM6fiIg6rOeeew6fffaZustod/jJn4iI\nSMsw/ImIiLQMw5+IiEjLMPyJiIi0DMOfiIhIyzD8iYiItAzDn4iISMsw/ImIiLQMw5+IiEjLMPyJ\niIi0DMOfiIhIyzD8iYiItAzDn4iISMsw/ImIiLQMw5+IiEjLMPyJiIi0DMOfiIhIyzD8iYiItAzD\nn4iISMsw/ImIiLQMw5+IiEjLMPyJiIi0DMOfiIhIyzD8iYiItAzDn4iISMuoPPz/+OMPhIaGYtiw\nYXB2dsYbb7yBkydPNqxPSkrCG2+8AZFIBB8fH3zyySeoqalpWC8WixEaGgoPDw+4u7sjNDQUYrFY\n1adBRESksVQa/uXl5ZgyZQrs7OyQkpKCCxcuwMfHB3PnzsWff/6J8+fPIyIiAiEhITh37hzWr1+P\nw4cPY/PmzQCAqqoqBAcHw9jYGElJSfj6669hZmaGoKAgVFVVqfJUiIiINJbKw3/evHkIDw9H165d\noa+vjylTpqCmpgbXrl3D7t27MWLECPj6+kJfXx9OTk4ICAhAYmIiamtrkZqaiqysLERGRsLc3BzG\nxsZYuHAhxGIxTp06pcpTISIi0lgqDX9zc3P4+/vD0NAQACCRSLBp0yZ0794d7u7uSE9Ph4uLi8xr\nXFxcUFpailu3biE9PR12dnYwMzNrWG9qagpbW1tcunRJladCRESksTqp68ADBgxAVVUVnJ2dsX37\ndpiZmaGkpAQmJiYy29UHfUlJCSQSSaP19dsUFxerpG4iIiJNp7be/pmZmTh79ixGjhyJSZMm4ebN\nm23an0AgUFBlREREHZtaH/UzNzfHnDlzYGVlhX379sHS0hKlpaUy20gkEgCAUCiEhYVFo/X121ha\nWqqkZiIiIk2n0vBPSUmBl5cXHj16JLO8srISurq6EIlEje7dX7hwAUKhEHZ2dhCJRBCLxTJN/Hfu\n3EF2djaGDBmiknMgIiLSdCoNf5FIhPLyckRHR6O0tBSPHj3Czp07kZ2dDR8fH0ybNg2pqak4duwY\nKisrcfnyZezYsQOBgYEQCATw9PSEg4MDYmNjIZFIUFJSgpiYGDg6OsLDw0OVp0JERKSxVN7bf9eu\nXSgoKMCoUaPg4eGBpKQkbNiwAa6urnB1dcWaNWuwadMmDBo0CHPmzMHUqVMxffp0AICuri7i4+NR\nXl4OLy8vjB49GtXV1YiPj4eurq4qT4WIiEhjCaRSqVTdRShbTk4OvL29kZKSgh49eqi7HCIiIqVr\nKfs4tj8REZGWYfgTERFpGYY/ERGRlml1+JeVlSEnJwdlZWXKqIeIiIiU7InD+1ZXV+PQoUM4efIk\nzp8/j4qKioZ1BgYGGDp0KF566SW8/vrr6NRJbaMFK11aGpCcDOTnA9bWgK8v4Oam7qqIiIhar8W0\n/vbbbxEbG4u8vDz069cPEydOhFAohLGxMe7du4eioiKcP38eixcvxqZNm7Bo0SJ4e3urqnaVSUsD\nEhL++j4396/v+QsAERFpmmbD/9NPP8X27dsxfvx4zJw5E1ZWVs3upKCgAPHx8fjggw8wY8YMzJ07\nVynFqktyctPLjx9n+BMRkeZp9p7/sWPH8PnnnyMqKqrF4AcAKysrLF68GF988QWOHTum8CLVLT+/\n6eV5eaqtg4iISBGaDf8DBw7AycnpiTuora2FWCwGADg6OuJ///uf4qprJ6ytm15uY6PaOoiIiBSh\n2fDv2rWrXDsoKSmBj49Pq1+nSXx9m14+dqxq6yAiIlIEhXTP7+gjBNff1z9+vK6p38amLvh5v5+I\niDSRQsJfIBAoYjftmpsbw56IiDoGjvBHRESkZRj+REREWqbZZv/9+/fLtQMO80tERKRZmg3/JUuW\nyL0TbbjnT0RE1FE0G/4pKSmqrIOIiIhUpNnw/8c//qHKOoiIiEhF2nzPv97EiRPbXAwREREpn9z3\n/AUCQaPBfB6/18/wJyIi0gxy3fPPzs7GihUr8Pbbb0MkEqFLly64d+8eLly4gIMHDyI2NlYlxRIR\nEVHbyXXPf/HixZg9e7bMGP4A0L9/fwiFQqxatQo7d+5UXpVERESkMHIN8nPx4kX06dOnyXX9+/fH\npUuXFFoUERERKY9c4d+1a1d88803Ta5LSUmBoaGhQosiIiIi5ZFrYp+JEyciLi4O3333Hfr27QsD\nAwNUVFTg8uXLyMjIwPTp05VdJxERESmIXOE/Z84cdO/eHYcOHcJXX32FBw8ewNDQEL169UJkZCSm\nTp2q7DqJiIhIQZoN/7t378LExKThe39/f/j7+6ukKCIiIlKeZsPf3d0d/fv3h6enJzw9PSESidCp\nk1wNBURERNSONZvmn3/+Oc6cOYPU1FQkJCRAX18fQ4cObfhloFevXqqsk4iIiBSk2fAfMGAABgwY\ngJkzZ+LBgwf46aefcObMGezevRuxsbGwtraGh4cHPD094eHhAVNTU1XWTURERE9Jrnb8Ll26wNvb\nG97e3gAAsViMH374AT/++COioqLw8OFDXL16VamFEhERkWI81U18W1tbTJo0CZMmTUJNTQ1++eUX\nRddFRERESiJX+K9Zs6bF9fr6+sjNzYW3tze6du2qkMKIiIhIOeQK/71796K8vBzV1dWN1j0+25+F\nhQU+++wzPPfcc4qtkoiIiBRGruF99+7dC0dHRyxbtgw//PADrly5gjNnziAqKgoikQjffPMNjh49\nCgcHB8TFxSm7ZiIiImoDucJ/yZIlmDlzJvz9/SEUCqGrqwsLCwtMmjQJ06ZNw7Jly9C7d28sWLAA\nV65cUXbNRERE1AZyhX9mZmazTflOTk64cOECAMDY2BgPHjxQXHVERESkcHKFv4WFBfbs2dPkuv/9\n73/o3LkzAODQoUN49tlnFVYcERERKZ5cHf5mzJiBZcuWISUlBX369EHnzp1RXl6OX3/9FQUFBZg9\nezaKioqwadMmrFy5Utk1ExERURvIFf6TJ0+GnZ0dvvrqK4jFYty8eRP6+voYMGAA5s+fj1deeQUA\nsG3bNnh6eiq1YCIiImobuQf5eeGFF/DCCy+0uA2Dn4iIqP2TO/zLy8uRlZWFsrKyhuf6H+fm5qbQ\nwoiIiEg55Ar/Y8eOISoqCg8ePGgy+AUCAX799Ve5DlhcXIy4uDj88MMPePjwIRwcHBAeHg53d3es\nX78eGzduhJ6ensxrZsyYgbCwMAB18wrExsYiIyMDUqkUAwcOxKJFi2BrayvX8RUpLQ1ITgby8wFr\na8DXF+DvQERE1N7JPbzviy++iMDAQJiYmEAgEDz1AWfNmoWuXbvi0KFDMDY2xoYNGzBr1iwcP34c\nQF0LQmJiYpOvraqqQnBwMFxcXJCUlIROnTphxYoVCAoKQlJSUqNfGpQpLQ1ISPjr+9zcv77nLwBE\nRNSeyRX+xcXFeP/999v86fr+/fvo3bs3ZsyYAaFQCAAIDg5GfHw8MjIynvj61NRUZGVlYe/evTAz\nMwMALFy4EB4eHjh16hRGjx7dpvpaIzm56eXHjzP8iYiofZPrOf9Bgwbh999/b/PBjIyMsHz5cvTu\n3bthmVgsBgB0794dAHD79m0EBgZi2LBh8PLywsqVK1FRUQEASE9Ph52dXUPwA4CpqSlsbW1x6dKl\nNtfXGvn5TS/Py1NpGURERK0m1yf/2NhYREZG4vr16w3P+f/d03T4KysrQ2RkJLy9veHs7IyrV6/C\nzs4OYWFh6NOnD9LT0xEeHo6HDx9i6dKlkEgkMDExabQfMzMzFBcXt/r4bWFtXdfU/3c2Niotg4iI\nqNXkCv+zZ8/i4sWLOHv2bJPrW9Phr15ubi5CQ0NhaWnZMBnQxIkTMXHixIZt3NzcEBISgtWrV2Px\n4sUt7q8t/RCehq+v7D3/emPHqrQMIiKiVpMr/NeuXYsxY8Zg2rRpbe7wBwAZGRkIDQ2Fj48PFi1a\n1GJHPXt7e1RWVkIikcDCwgKlpaWNtpFIJLC0tGxTTa1V39Bx/HhdU7+NTV3w834/ERG1d3KF/717\n9zBnzhyFPE537do1BAcH491330VAQIDMus2bN6Nv37548cUXG5Zdv34dnTt3hqWlJUQiEbZs2YLi\n4mJYWFgAAO7cuYPs7GwMGTKkzbW1lpsbw56IiDSPXB3+hg4diqtXr7b5YDU1NYiIiIC/v3+j4AeA\n0tJSREVF4fLly6iurkZaWhoSEhIQGBgIgUAAT09PODg4IDY2FhKJBCUlJYiJiYGjoyM8PDzaXB8R\nEZE2kOuT/zvvvIM1a9bgt99+Q79+/WBoaNhom+HDhz9xP7/88guuXLmCa9euYefOnTLr/Pz8EBUV\nBQMDA4SFhaGwsBBCoRBBQUGYNm0aAEBXVxfx8fGIjo6Gl5cXBAIBPDw8EB8fD11dXXlOhYiISOsJ\npE0N2fc3ffr0afrFAgGkUulTdfhTpZycHHh7eyMlJQU9evRQdzlERERK11L2yfXJf9euXUoprCPi\nkL9ERNTeNRv+V65cQf/+/QHU3fOX19WrV9GvX7+2V6aBOOQvERFpgmY7/E2ZMgX79u1r1c727duH\nKVOmtLkoTdXSkL9ERETtRbPhv2nTJqxduxbjxo3DkSNHcPfu3Sa3u3v3Lg4fPoxx48Zh7dq12Lhx\no9KKbe845C8REWmCZpv93d3dcfDgQXzyySdYuHAhBAIBevXqBaFQiK5du6KsrAyFhYW4ceMGAODl\nl1/G1q1bYaPF49tyyF8iItIELXb4s7GxwerVqzFnzhycPHkSaWlpKCoqQm5uLoyMjGBra4vx48fD\n29sbdnZ2qqq53eKQv0REpAnk6u1vZ2eH6dOnY/r06cquR6NxyF8iItIEcoU/yY9D/hIRUXsn1/C+\nRERE1HEw/ImIiLQMw5+IiEjLMPyJiIi0jNwd/vLy8pCZmYnS0lI0NRfQxIkTFVoYERERKYdc4X/g\nwAEsWbIE1dXVTa4XCAQMfyIiIg0hV/hv2bIFXl5eCAoKgrm5OQQCgbLrIiIiIiWRK/yLioqQkJAA\ne3t7ZddDRERESiZXh79evXpBIpEouxYiIiJSAbnCf+HChfj4449x/fp1ZddDRERESiZXs/+qVatw\n584dvPrqqzA0NETnzp1l1gsEAvzwww9KKZCIiIgUS67wd3R0hKOjo7JrISIiIhWQK/xXrFih7DqI\niIhIReQe5Ke2thbnzp3D1atX8eDBAxgbG8PZ2RmDBw9WZn1ERESkYHKFf0FBAYKCgvDHH3/ILBcI\nBBg0aBC2bNkCIyMjpRRIREREiiVXb//Vq1ejsrISCQkJSEtLw9WrV3Hu3Dls2rQJ+fn5iIuLU3ad\nREREpCByhf+ZM2cQHR2N4cOHw8jICDo6OjAxMcGoUaOwZMkSpKSkKLtOIiIiUhC5wv/+/fuwsbFp\ncl2vXr1QWlqq0KKIiIhIeeS6529jY4MffvgBkyZNarTuzJkzsLa2VnhhHUVaGpCcDOTnA9bWgK8v\n4Oam7qqIiEibyRX+48ePx8qVK3Hz5k2IRCJ07doVZWVluHDhAr744gvMmTNH2XVqpLQ0ICHhr+9z\nc//6nr8AEBGRusgV/iEhIXj48CESExORmJjYsLxLly4IDg5GcHCw0grUZMnJTS8/fpzhT0RE6iNX\n+AsEAoSHh2P27Nm4ceMGysrKYGRkhJ49e0JPT0/ZNWqs/Pyml+flqbYOIiKix8k9yA8A6OnpwcnJ\nSVm1dDjW1nVN/X/XTN9JIiIilWg2/IcPH44jR47AzMwMw4cPb3EnnNinab6+svf869nZAdHR7ARI\nRETq0Wz4v/DCCw1N+sOHD4dAIFBZUR1FfaAfP17X1G9jUxf8P/741zbsBEhERKrWbPg/PpnPf/7z\nn2Z38OjRIz7n3wI3N9lQj45uejt2AiQiIlWRa5Cfvn37oqSkpMl1N2/ehJ+fn0KL6sjYCZCIiNSt\nxQ5/X375JQBAKpXi2LFj6Nq1q8x6qVSK8+fP49GjR8qrsINhJ0AiIlK3FsP/wIEDyMzMhEAgQExM\nTLPbTZkyReGFdVTNdQIcO1b1tRARkXZqMfwTExNRXV2NAQMGYP/+/TAzM2u0jbGxMUxNTZVWYEfT\nVCfA+uDnEwBERKQKT3zOv1OnTkhJSYGNjU2TPf7v37+PLVu2IDQ0VCkFdkR/7wTIYYCJiEiV5Brk\n5x//+AcqKytx7do1mZ79UqkUly5dQnx8PMO/DTgMMBERqZJc4f/7779j5syZKCgoaHL9Sy+9pNCi\ntA2fACAiIlWS61G/1atXw9HREVu3boW+vj5WrVqF1atXw8PDA2+99RbWrVsn9wGLi4sRGRmJ4cOH\nY9CgQZgwYQLOnj3bsD4pKQlvvPEGRCIRfHx88Mknn6CmpqZhvVgsRmhoKDw8PODu7o7Q0FCIxeJW\nnHL709yMyHwCgIiIlEGu8M/MzMS8efMwYsQI6OjowNXVFa+++iq2bduG8vJyxMfHy33AWbNmobCw\nEIcOHcLZs2cxbNgwzJo1CwUFBTh//jwiIiIQEhKCc+fOYf369Th8+DA2b94MAKiqqkJwcDCMjY2R\nlJSEr7/+GmZmZggKCkJVVdXT/Qu0A76+TS/nEwBERKQMcoV/WVkZTExMAAAGBgYoKytrWBcUFIS9\ne/fKdbD79++jd+/e+PDDDyEUCvHMM88gODgYDx8+REZGBnbv3o0RI0bA19cX+vr6cHJyQkBAABIT\nE1FbW4vU1FRkZWUhMjIS5ubmMDY2xsKFCyEWi3Hq1KmnOP32wc0NCAoCevQAdHTqvgYF8X4/EREp\nh1z3/O3s7HD69Gn4+/vDxsYG3333Hfr27QsAePjwodzD+xoZGWH58uUyy+qb7Lt374709HRMmjRJ\nZr2LiwtKS0tx69YtpKenw87OTuaRQ1NTU9ja2uLSpUsYPXq0XHW0R39/AoCIiEhZ5Ap/f39/REVF\nYdCgQfD19cXHH3+MW7duwcTEBCdOnICzs/NTHbysrAyRkZHw9vaGs7MzSkpKGloY6tUHfUlJCSQS\nSaP19dsUFxc/VQ1ERETaRq7wDwwMhJmZGaysrBAQEIDbt2/jyJEjqKqqgkgkwpIlS1p94NzcXISG\nhsLS0hJxcXGtfv3fcdZBIiIi+cgV/lVVVXj99dcbvv/3v/+Nf//730990IyMDISGhsLHxweLFi1q\nmDrY0tKy0S0EiUQCABAKhbCwsGjyFoNEIoGlpeVT19NepaXVjQHAUf+IiEiR5OrwN3jwYNy+fVsh\nB7x27RqCg4MREhKCjz76qCH4AUAkEuHSpUsy21+4cAFCoRB2dnYQiUQQi8UyTfx37txBdnY2hgwZ\nopD62ov6Uf9yc4Ha2r9G/UtLU3dlRESk6eSe0vfcuXNtPlhNTQ0iIiLg7++PgICARuunTZuG1NRU\nHDt2DJWVlbh8+TJ27NiBwMBACAQCeHp6wsHBAbGxsZBIJCgpKUFMTAwcHR3h4eHR5vrak5ZG/SMi\nImoLuZr9J0yYgP/+9784ffo0+vfvjy5dujTaZuLEiU/czy+//IIrV67g2rVr2Llzp8w6Pz8/xMTE\nYM2aNVi3bh0WLFgAS0tLTJ06FdOnTwcA6OrqIj4+HtHR0fDy8oJAIICHhwfi4+Ohq6srz6loDI76\nR0REyiKQSqXSJ23Up0+flnciEODXX39VWFGKlpOTA29vb6SkpKBHjx7qLkcu0dF1Tf1/16MHsHgx\n+wMQEVHLWso+uT75p6SkKKUwap6vr+xMf/XGjuUsgERE1DZy3fM/dOgQzM3N8Y9//KPRn9raWuza\ntUvZdWpz9Gm+AAAfDklEQVSdlkb9Y38AIiJqC7k++W/cuBGTJk2CoaFho3UFBQX4v//7P0RGRiq8\nOG3X3Kh/7A9ARERt0WL413eqk0qlGD9+PHR0ZBsKpFIp7ty5A+vmpqUjpbC2bro/AGcBJCIiebQY\n/itWrMDFixfx6aefwtnZGc8880yjbUxMTDBhwgSlFUiNtdQfgIiI6ElaDP9hw4Zh2LBhyM7OxqJF\ni9C1a1dV1UUtqL8VcPx4XVO/jU1d8LOzHxERyUOue/4rVqxo+HtVVRWaejpQX19fcVXRE3EWQCIi\nelpyhf+tW7cQHR2N9PR0lJeXN1ovEAhw9epVhRdHREREiidX+C9evBg3btyAn58fzM3NOYMeERGR\nBpMr/DMzM/Hf//63w02eQ0REpI3kGuTHyMioQ06ZS0REpI3kCn9/f3988cUXyq6FiIiIVECuZn9T\nU1Ps3bsX586dg6urKzp37iyzXiAQIDw8XCkFEhERkWLJFf6xsbENf8/MzGy0nuFPRESkOeQK/99+\n+03ZdRAREZGKyHXP/+8kEglqa2sVXQsRERGpgNzh/+2332LChAlwdnaGp6cncnNzcf/+fSxatAjV\n1dXKrJGIiIgUSK7wP3r0KGbNmoUuXbpg9uzZ6NSp7m7Bw4cPcfbsWWzcuFGpRRIREZHiyBX+W7du\nxezZs7Fjxw7MnDkTurq6AAArKyv8+9//xldffaXUIomIiEhx5Ar/rKwsjBs3rsl1ffr0QWFhoUKL\nIiIiIuWRq7e/hYUF8vLyYG9v32hddnY2jI2NFV4YPb20NCA5GcjPB6ytAV9fzgBIRER/keuT/6BB\ng7BkyRL8/PPPMtP5/vnnn1i5ciVGjhyptAKpddLSgIQEIDcXqK2t+5qQULeciIgIkDP8Fy5ciE6d\nOmHq1KkYOHAgysvL8dprr2HcuHGorq7G/PnzlV0nySk5uenlx4+rtg4iImq/5Gr2FwqF+Oqrr3Di\nxAlkZGSgrKwMxsbGcHV1xahRo6Cnp6fsOklO+flNL8/LU20dRETUfskV/gCgp6eHkSNH4uWXX25Y\ndufOHQZ/O2NtXdfU/3c2NqqvhYiI2ie5mv1LS0sxffp0xMTEyCyfO3cupk+fjnv37imlOGo9X9+m\nl48dq9o6iIio/ZIr/OPi4pCbm4vXXntNZvncuXNRVFSEVatWKaU4aj03NyAoCOjRA9DRqfsaFMTe\n/kRE9Be5mv1PnTqFdevWQSQSySx//vnnER0djblz5yqlOHo6bm4MeyIiap5cn/zrO/g1xdzcHGVl\nZQotioiIiJRHrvAfMGAAdu3aJfOMPwBUVVVh3bp16Nevn1KKI9VISwOio4F33637yjEBiIg6Nrma\n/f/1r38hMDAQJ0+eRN++fdGlSxfcu3cPmZmZqKqqwvbt25VdJylJ/aBA9eoHBQJ464CIqKOS65O/\nq6srDh48iDFjxuDevXv4/fffUVlZiTfffBNHjhzBoEGDlF0nKQkHBSIi0j5yP+ffs2dPREVFKbMW\nUgMOCkREpH3kDv/y8nLcuHEDpaWlje79A8Dw4cMVWhipBgcFIiLSPnKF//fff48FCxbg/v37TQa/\nQCDAr7/+qvDiSPl8fWXv+dfjoEBERB2XXOEfFxcHBwcHTJ06Febm5hAIBMqui1SkvlPf8eN1Tf02\nNnXBz85+REQdl1zhLxaLceDAATg4OCi7HlIDDgpERKRd5Ort36NHD1RXVyu7FiIiIlIBucJ/zpw5\nWLNmDe7evavseoiIiEjJ5Gr2P3bsGLKysjBixAjY2dmhS5cujbbZt2+fwosjIiIixZMr/CUSCbp1\n64Zu3bopux4iIiJSMrnCPzExUdl1EBERkYrIdc+/XnZ2No4fP44DBw7gxIkTuH37dqsPKBaLMXXq\nVDg5OSEnJ6dh+fr169GnTx84OzvL/Fm7dq3Ma0NDQ+Hh4QF3d3eEhoZCLBa3ugYiIiJtJtcn/7Ky\nMoSFheHMmTMyg/zo6OjglVdewfLly6Gnp/fE/Zw4cQJLlizBCy+80OR6Nze3ZlsZqqqqEBwcDBcX\nFyQlJaFTp05YsWIFgoKCkJSUJNfxiYiISM5P/nFxcfjtt9/w0Ucf4csvv8SJEydw8OBBfPjhhzh9\n+jTWrVsn18FKS0uxZ88e+Pn5tbrQ1NRUZGVlITIyEubm5jA2NsbChQshFotx6tSpVu+PiIhIW8n1\nyf/bb79FdHQ0vLy8ZJb369cP5ubmWL16Nf71r389cT/+/v4AgPxmZpO5ffs2AgMDcfXqVXTp0gVj\nxozB+++/DwMDA6Snp8POzg5mZmYN25uamsLW1haXLl3C6NGj5TkVIiIirSdX+JeUlOC5555rcp2z\nszOKioraXEi3bt1gZ2eHsLAw9OnTB+np6QgPD8fDhw+xdOlSSCQSmJiYNHqdmZkZiouL23x8IiIi\nbSFXs79QKERGRkaT6y5fvgxLS8s2FzJx4kRs27YNzs7O0NPTg5ubG0JCQnDw4MEnji7IuQaIiIjk\nJ9cn/7FjxyI6OhpFRUUYNGgQunTpgrKyMly4cAFbt27FP//5T6UUZ29vj8rKSkgkElhYWKC0tLTR\nNhKJRCG/fBAREWkLucI/PDwchYWFWLlypcxygUCA1157DWFhYW0uZPPmzejbty9efPHFhmXXr19H\n586dYWlpCZFIhC1btqC4uBgWFhYAgDt37iA7OxtDhgxp8/GJiIi0hVzhr6+vj48//hgLFizAlStX\nUFZWBiMjI/Tr1w9WVlYKKaS0tBRRUVHYuHEj+vbti19++QUJCQkIDAyEQCCAp6cnHBwcEBsbi8WL\nF0MqlSImJgaOjo7w8PBQSA1ERETa4InhX1lZCX19fQCAlZWVTNjfu3evVQcbM2YM8vLyGsYKGDt2\nLAQCAfz8/BAVFQUDAwOEhYWhsLAQQqEQQUFBmDZtGgBAV1cX8fHxDU8dCAQCeHh4ID4+Hrq6uq2q\ng4iISJsJpI+P2vM3ycnJWLlyJZKTk2FoaCiz7ssvv8Tq1auxdu1auLXzyeBzcnLg7e2NlJQU9OjR\nQ93laLS0NCA5GcjPB6ytAV9foJ1ffiIirdRS9jXb2//q1atYsGABevfujfLy8kbrhw8fjoEDB2LW\nrFkcYldLpKUBCQlAbi5QW1v3NSGhbjkREWmOZsN/x44dGDRoEBISEmBubt5ovaWlJTZs2IC+ffsi\nISFBqUVS+5Cc3PTy48dVWwcREbVNs+F/4cIFBAcHt/gMvY6ODmbOnImffvpJKcVR+9LMwIzIy1Nt\nHURE1DbNhn9RURF69uz5xB08++yzTzW7H2kea+uml9vYqLYOIiJqm2bDv0uXLrh79+4Td1BUVITO\nnTsrtChqn3x9m14+dqxq6yAiorZpNvxdXFxw9OjRJ+5g//79GDhwoEKLovbJzQ0ICgJ69AB0dOq+\nBgWxtz8RkaZp9jn/yZMnY9asWXj22WcbZuN7nFQqxebNm/Hll19i+/btSi2S2g83N4Y9EZGmazb8\nR44ciaCgICxevBg7d+7EiBEjYGNjg9raWmRnZ+O7775DXl4e3n33Xbi7u6uyZiIiImqDFkf4Cw8P\nx6BBg7B9+3bs3r0blZWVAABDQ0MMGTIEy5Yt49C6REREGuaJw/uOHDkSI0eORE1NDSQSCQQCAczM\nzKCjI9dswERERNTOyDWxD1A3tj6nziUiItJ8/PhORESkZRj+REREWobhT0REpGUY/kRERFqG4U9E\nRKRlGP5ERERahuFPRESkZeR+zp9IFdLSgORkID+/bgphX1/OJUBEpGgMf2o30tKAhIS/vs/N/et7\n/gJARKQ4bPandiM5uenlx4+rtg4ioo6O4U/tRn5+08vz8lRbBxFRR8fwp3bD2rrp5TY2qq2DiKij\nY/hTu+Hr2/TysWNVWwcRUUfHDn/UbtR36jt+vK6p38amLvjZ2Y+ISLEY/tSuuLkx7ImIlI3N/kRE\nRFqG4U9ERKRlGP5ERERahuFPRESkZRj+REREWobhT0REpGUY/kRERFqG4U9ERKRlGP5ERERahuFP\nRESkZRj+REREWobhT0REpGU4sQ9plbQ0IDkZyM8HrK3rphHmREJEpG0Y/qQ10tKAhIS/vs/N/et7\n/gJARNqEzf6kNZKTm15+/Lhq6yAiUjeGP2mN/Pyml+flqbYOIiJ1U3n4i8ViTJ06FU5OTsjJyZFZ\nl5SUhDfeeAMikQg+Pj745JNPUFNTI/Pa0NBQeHh4wN3dHaGhoRCLxao+BdJQ1tZNL7exUW0dRETq\nptLwP3HiBCZOnAibJn7anj9/HhEREQgJCcG5c+ewfv16HD58GJs3bwYAVFVVITg4GMbGxkhKSsLX\nX38NMzMzBAUFoaqqSpWnQRrK17fp5WPHqrYOIiJ1U2n4l5aWYs+ePfDz82u0bvfu3RgxYgR8fX2h\nr68PJycnBAQEIDExEbW1tUhNTUVWVhYiIyNhbm4OY2NjLFy4EGKxGKdOnVLlaZCGcnMDgoKAHj0A\nHZ26r0FB7OxHRNpHpb39/f39AQD5Tdx8TU9Px6RJk2SWubi4oLS0FLdu3UJ6ejrs7OxgZmbWsN7U\n1BS2tra4dOkSRo8erdziqUNwc2PYExG1mw5/JSUlMDExkVlWH/QlJSWQSCSN1tdvU1xcrJIaiYiI\nOoJ2E/5tIRAI1F0CERGRxmg34W9paYnS0lKZZRKJBAAgFAphYWHRaH39NpaWliqpkYiIqCNoN+Ev\nEolw6dIlmWUXLlyAUCiEnZ0dRCIRxGKxTBP/nTt3kJ2djSFDhqi6XCIiIo3VbsJ/2rRpSE1NxbFj\nx1BZWYnLly9jx44dCAwMhEAggKenJxwcHBAbGwuJRIKSkhLExMTA0dERHh4e6i6fiIhIY6i0t/+Y\nMWOQl5cHqVQKABg7diwEAgH8/PwQExODNWvWYN26dViwYAEsLS0xdepUTJ8+HQCgq6uL+Ph4REdH\nw8vLCwKBAB4eHoiPj4eurm6b6uJkL6QIfB8RkaZQafh//fXXLa738fGBj49Ps+utra0bBv1RFE72\nQorA9xERaRKtn9Wvpcle+EOb5MX3ERGpiiJaGbU+/DnZCykC30dEpAqKamVsNx3+1IWTvZAi8H1E\nRKqgqKnJtT78OdkLKQLfR0SkCopqZdT6Zv/6ZpLjx+v+8Wxs6n5g8z4ttQbfR0SkCtbWdU39f9fa\nVkatD3+Ak72QYvB9RETK5usre8+/XmtbGRn+RESkdTR1XA5FtTIy/Ik0hKb+sCJqbzR9XA5FtDJq\nfYc/Ik1Q/8MqNxeorf3rh1VamrorI9I8iuoxr8n4yZ9IA2j6IEJstWieJv/baGrtHJeD4U+kETT5\nh5Wym1g1NYAAzW5+1uTaFdVjXpOx2Z9IA2jyIELKbGLV9Nshmtz8rMm1c1wOhj+RRtDkH1bKbLXQ\n5AACNLtFR5Nrd3MDgoKAHj0AHZ26r0FB7b/FQpHY7E+kATR5ECFlNrFqcgABmt38rMm1AxyXg+FP\npCGU+cNKmffNFTUoSVM0PYCU+W+jbJpcOzH8ibSesjtuKbPVQtMDSJNbdDS5dmL4E2k9VTxGqKxW\ni44QQJrc/KzJtWs7hj+RltP0++YMIKLWY29/Ii2nyY8REtHTYfgTaTlNfoyQiJ4Om/2JtFxHuG9O\nRK3D8Cci3jcn0jJaEf41NTUAgNu3b6u5EiIiItWoz7z6DHycVoR/UVERAGDy5MlqroSIiEi1ioqK\nYG9vL7NMIJVKpWqqR2UqKiqQmZkJoVAIXV1ddZdDRESkdDU1NSgqKsKAAQNgYGAgs04rwp+IiIj+\nwkf9iIiItAzDn4iISMsw/ImIiLQMw5+IiEjLMPyJiIi0jFY859+S8vJyrFy5EqdPn8bdu3fh4OCA\nuXPnwtPTU92ltVlxcTHi4uLwww8/4OHDh3BwcEB4eDjc3d2xfv16bNy4EXp6ejKvmTFjBsLCwtRU\n8dPz8vJCQUEBdHRkf589fPgwevbsiaSkJGzbtg23bt2CUCiEr68v5s6dq3GPfqalpWH69OmNlldX\nV+P111+HjY2NRl9XsViMDz/8EOfPn0dKSgp69OjRsO5J11AsFiM2NhYZGRmQSqUYOHAgFi1aBFtb\nW3WdTotaOtc9e/Zgz549yM/Ph5mZGV5//XXMnj0bOjo6yMnJgbe3N/T09CAQCBpeIxQK8e2336rj\nVJ6ouXOV5+dQR7muY8aMQd7fpsqUSqWoqqrC77//rvrrKtVyERER0tdee01648YNaUVFhXTv3r3S\nAQMGSK9fv67u0tpswoQJ0unTp0sLCwulFRUV0ri4OKmrq6v09u3b0nXr1kmnTJmi7hIVZtSoUdID\nBw40ue7cuXPS/v37S48dOyZ99OiR9LfffpO++OKL0vXr16u4SuUoLCyUDh06VHru3DmNvq7ffPON\n1N3dXbpgwQKpo6OjVCwWN6x70jWsrKyUjhkzRjp//nxpcXGx9O7du9KIiAipj4+PtLKyUl2n1KyW\nznXv3r3SwYMHS8+dOyetrq6W/vzzz1KRSCT97LPPpFKpVCoWixu9pj1r6Vyf9H7tSNe1KeHh4dKI\niAipVKr666rVzf53797FkSNHMGfOHPTs2RPPPPMM3nrrLfTu3Rv79u1Td3ltcv/+ffTu3Rsffvgh\nhEIhnnnmGQQHB+Phw4fIyMhQd3kqtXv3bowYMQK+vr7Q19eHk5MTAgICkJiYiNraWnWX12ZLliyB\nr68vhg4dqu5S2qS0tBR79uyBn59fo3VPuoapqanIyspCZGQkzM3NYWxsjIULF0IsFuPUqVNqOJuW\ntXSulZWVmD9/PoYOHQpdXV0MHjwYzz//PH766Sc1VNp2LZ3rk3Sk6/p3J0+eRFpaGiIjI1VQWWNa\nHf5XrlxBVVUVnJ2dZZa7uLjg0qVLaqpKMYyMjLB8+XL07t27YZlYLAYAdO/eHUDduM+BgYEYNmwY\nvLy8sHLlSlRUVKilXkVITk7Gyy+/jMGDB+PNN9/EyZMnAQDp6elwcXGR2dbFxQWlpaW4deuWGipV\nnG+//RYXL17EvHnzGpZp6nX19/dHz549m1z3pGuYnp4OOzs7mJmZNaw3NTWFra1tu/y/3NK5vvPO\nO5g4cWLD91KpFLm5ubC2tpbZbs2aNRg1ahSGDRuGGTNm4I8//lBqzU+rpXMFWn6/dqTr+riKigpE\nR0dj4cKFMDY2llmnquuq1eFfUlICoO7N9DgzMzMUFxeroySlKSsrQ2RkJLy9veHs7Ixu3brBzs4O\nH3zwAVJTU7Fy5UocOXIEK1asUHepT8XR0RG9evXC7t27cerUKbz00kuYPXs20tPTUVJSAhMTE5nt\n63+Y1L8HNFFtbS3WrFmDkJAQdO3aFQA63HWt96RrKJFIGq2v30bT/y9v3LgReXl5DX099PX1MWDA\nAAwbNgzJyck4fPgwDAwMEBgYiPv376u52tZ50vu1o17XXbt2wdTUFK+88krDMlVfV60O/5Y83uFC\n0+Xm5uLtt9+GhYUF4uLiAAATJ07Etm3b4OzsDD09Pbi5uSEkJAQHDx5EdXW1mituvS1btjQ0DXbt\n2hXvvvsu+vbti88//1zdpSnNN998g4KCApkJqzradVUETf2/XFNTg9jYWCQmJiI+Pr6h41i3bt1w\n4MABTJw4EQYGBrCyssLy5ctRXFyMlJQUNVfdOm15v2rqda2srMS2bdswc+ZMmXNQ9XXV6vC3sLAA\nUHef5nESiQSWlpbqKEnhMjIy4O/vj8GDByM+Ph6dO3dudlt7e3tUVlZCIpGosELlsbOzQ0FBASwt\nLZu8xkBdT1pNdfjwYXh5eeGZZ55pcbuOcF2fdA0tLCwara/fRhP/L1dUVODdd9/FmTNnsH//fohE\noha3NzExgampKQoLC1VUofI8/n7taNcVAE6fPo2KigqMGjXqidsq87pqdfgPGDAA+vr6SE9Pl1l+\n8eJFDBkyRE1VKc61a9cQHByMkJAQfPTRRzKP02zevBnff/+9zPbXr19H586dNe4/lVgsxtKlS3Hv\n3j2Z5Tdu3IC9vT1EIlGj+4MXLlyAUCiEnZ2dKktVmLKyMpw+fRqjR4+WWd6RruvjnnQNRSIRxGKx\nTFPwnTt3kJ2drXH/l2tqajB79myUl5dj//79ePbZZ2XW//jjj/j0009lltXf+tC09/OT3q8d6brW\nS05OhoeHR6MPYqq+rlod/kZGRhg/fjzWr1+Pmzdvory8HNu2bUNubi7eeustdZfXJjU1NYiIiIC/\nvz8CAgIarS8tLUVUVBQuX76M6upqpKWlISEhAYGBgRrXnGZpaYmUlBQsXboUEokEDx8+xIYNG3Dz\n5k1MmTIF06ZNQ2pqKo4dO4bKykpcvnwZO3bs0Mhzrffrr7+iqqoKffv2lVneka7r4550DT09PeHg\n4IDY2FhIJBKUlJQgJiYGjo6O8PDwUHf5rZKYmIisrCxs2bIFRkZGjdYbGxsjPj4en332GR49eoSi\noiIsWrQI9vb28PLyUkPFT+9J79eOdF3rpaeno1+/fo2Wq/q6av2UvpWVlVi1ahWOHj2KBw8eoG/f\nvliwYAEGDx6s7tLa5Oeff8bkyZMbDRgBAH5+foiKisLGjRuRlJSEwsJCCIXChqDUtIFvgLpPC6tX\nr0Z6ejrKy8vRr18/LFy4EK6urgDq7o+vW7cOt27dgqWlJd56661G99w0ydGjR/HBBx8gPT0dhoaG\nDcsrKys19rrWD4Ii/f8Dn9S/d/38/BATE/PEa5ifn4/o6Gj89NNPEAgE8PDwwOLFi2FlZaXmM2us\npXM9d+4ccnNzm7xely9fBgCcOnUKGzduxPXr1wEAI0aMQEREhMadqzw/hzrKdY2JiQEAODs7IyIi\nQqavTj1VXletD38iIiJto9XN/kRERNqI4U9ERKRlGP5ERERahuFPRESkZRj+REREWobhT0REpGUY\n/kQdQEREBJycnFr8M3XqVADA1KlTMWHCBLXW++DBA4wbNw7/+c9/nnofOTk5cHJywt69e+Xavrq6\nGm+//TYWLFjw1Mck6ij4nD9RB3D//n2ZaXvnzJmDyspKbN26tWGZnp4eTE1NG8ZK//tslqr0/vvv\no6CgALt370anTp2eah81NTUoKSmBkZERDAwM5HpNQUEBXnvtNbz33nt45513nuq4RB3B0/2vI6J2\nxcjISGYoWD09PdTW1jY5cZE6Qx8Azp49i+PHj2P//v1PHfwAoKur2+qJmaysrBAcHIy1a9fi1Vdf\nhbm5+VMfn0iTsdmfSMv8vdnfyckJ27dvx/LlyzFs2DAMHjwYMTExqKiowJIlSzB06FC4u7tj1apV\nMvspLCzEvHnz4OXlBRcXF4wbNw5JSUlPPP6GDRvw/PPPNwy9DABeXl6IjY3F1q1bMXz4cIhEInzw\nwQcoLy/H2rVr4enpCTc3N0RGRqKyshJA42b/gwcPwsnJCX/++SeCg4MhEokwfPhwxMTEoKampuFY\nkydPhq6uLnbu3Nmmf0ciTcbwJyLs27cP5ubm+Pzzz/H+++8jMTERAQEB6NGjB7744gvMnDkT27Zt\nw/nz5wHUzSEQEBCA9PR0LFu2DF999RXGjBmDf/3rXzh58mSzxykpKcHFixebnM70+++/R2FhIXbt\n2oXly5fj2LFjCAwMRHl5OXbv3o2lS5fi0KFDOHr0aIvnsnjxYowfPx6HDx/GlClTkJiYKPNLiaGh\nIdzd3XHixImn/Nci0nwMfyKCubk5QkNDYW9vj6lTp6JLly4wMDBAcHAw7O3tMW3aNHTp0gVXr14F\nAJw8eRLXr19HbGwsPD090bNnT8yePRvu7u7YsmVLs8f5+eefUVtbi0GDBjVaV1VVhUWLFqFXr17w\n9fXFc889h5KSEkRERKBnz554+eWX8dxzzzXU0JxXXnkFY8eOha2tLUJCQtC5c+dG0wEPGTIE169f\nl5kqlkibMPyJCP3792/4u0AggImJicx0wfXLysrKAACXLl2Cnp4e3NzcZPbj7u6O3377Dc31Iy4q\nKgIAdOvWrdG6Pn36QEfnrx9JJiYm6NOnj8zMi4/X0JyBAwc2/F1HR0emk2O9+r4ChYWFLe6LqKNi\nhz8ikpkWGKgL+86dOzdaVh/qZWVlqKqqajT1dXV1NaqqqiCRSJrsTHfv3j0AQNeuXdtcQ3PkeY2x\nsTEANPqlgEhbMPyJqNWMjY1hYGCAL7/8stn1LS0vKytr8hcAVan/JUTdTz4QqQub/Ymo1VxdXVFR\nUYFHjx7B3t6+4c8zzzwDMzOzZh/hay/N7S3dfiDSBgx/Imq1UaNGwdHREfPnz8fZs2eRm5uLb7/9\nFm+//TZWrFjR7OuGDBkCHR0dXLhwQYXVNpaWlobevXvDwsJCrXUQqQvDn4haTV9fHzt27ECfPn0Q\nHh4OHx8fLFu2DK+99hqWLl3a7OvMzc0xaNAgfP/996or9m8qKirw008/4aWXXlJbDUTqxuF9iUil\nzpw5g+nTp+OLL76Ai4uLyo+/fft2rF+/HikpKRzhj7QWP/kTkUp5enrCx8cHK1askBl5TxUKCwsR\nHx+P999/n8FPWo3hT0Qq95///AdlZWWIi4tT2TGrq6sRFhaGF154AQEBASo7LlF7xGZ/IiIiLcNP\n/kRERFqG4U9ERKRlGP5ERERahuFPRESkZRj+REREWobhT0REpGX+HzaCX5bOjYhlAAAAAElFTkSu\nQmCC\n", + "image/png": "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\nJ4fo6GjWrFmj3v/u3btUVFRQVlb2SPMF1kUSiBBC79nZVT22up+ul1wwNDSksrKywftXVFSovzcy\nMnrgtqampjXKqmeaUiqVdOrUib///e+cPn2aEydOcPToUd577z1WrVrF4MGDAYiKisLX17fGcZRK\n3XzUSyO6EELvNdeSC66urty4cYMrV66oy/79739TWFhYY9vqBHH79m112S+//KL+vmvXriiVSlJT\nU9VllZWVDB06lEOHDuHs7ExKSorGMU+fPg1UPc76/PPP+eijj+jduzcREREcOHCAAQMG8MUXX9Ch\nQwdsbGzIzMykW7du6q9//OMfbN++HQMD3XzUSwIRQui93r1h2jRwdAQDg6p/p03T/Qzaffv2xd3d\nncjISFJTUzl37hyRkZFA1ezg93JxccHMzIyEhASuXLnCsWPHeP/999X1ZmZmTJw4kdjYWI4ePcov\nv/zC8uXLuXXrFn369GH69OmcP3+e6Oho/v3vf3P8+HGWLVuGv78/zs7OlJWVER0dzd///neuXr3K\n999/zz//+U88PDwACA0N5YMPPmDPnj1cuXKFgwcPsnr1aqysrHR2feQRlhCiRWiuJRfi4+NZtmwZ\nwcHBdOjQgRkzZpCamlrjkVT79u2JiYlhzZo1DB8+HDc3N15//XWN5Sb++te/YmhoyMKFCykqKqJH\njx5s376dLl260KVLFxISEli/fj07d+7kscceY8SIEbz66qsAjB49moKCAjZu3EhWVhadO3dm7Nix\nvPzyywBMmDCBsrIytm/fzooVK7CxsWHWrFnMmDFDZ9dGpnMXQog6XL9+nXPnztG/f38MDQ0ByMvL\nw8/Pj927d6sbuVurh312yh2IEELUwdDQkDlz5jB16lQCAwMpKipiw4YNdOvWTf3oqC2TNhAhhKhD\nx44dSUhI4IcffmDkyJFMnjwZpVLJe++999BeVW1Bve5ASkpKyMnJobCwEEtLS6ysrHTSt1gIIfRF\nv3796NevX3OHoZcemkDKysrYu3cvn332GefOndPo12xoaIivry9Dhw5lzJgxkkyEEKINeWAC2b9/\nP2vXrqWsrIznnnuOYcOG4eDggJmZGbdu3SI7O5szZ86wbt064uPjmT17NkFBQU0VuxBCiGZUZwKZ\nOXMm169fZ9myZQwYMKDOu4upU6dSVlbGl19+yfvvv8/XX3/Nu+++q7OAhRBC6Ic6E0hAQACjR4/W\n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\nEbVbKAAAAABJRU5ErkJggg==\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -290,9 +290,9 @@ "outputs": [ { "data": { - "image/png": "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\n2Cc9+RpIyCQAAAAASUVORK5CYII=\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -326,9 +326,9 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\nqqoytZ08eZIzZ87QrVs3a5crIiIOLiQkhL/97W9Wea8NGzbw29/+1ibvfTG7HzKYPXs2rq6uLF26\nlPPnz3Pq1Cmg9sZD7dq1Y8iQISQkJDBv3jwmT57Mv//9b1588UV69+7NgAEDbFy9iIhcq4yTGWzN\n2Up+ST6+Xr6MCB5hlXu/7N69m1atWjX5+9jbe9t1IDh27Bj79+8HYPhw81sCd+7cmc8++4zOnTuz\ndu1aXnnlFaKjo3FxceH2229nzpw5tihZREQsIONkBqv3rjY9PvnTSdPjpg4FtrynjS3f2+4Cwbp1\n60x/vummm/juu++u+JqwsDCz14mIiGO73BLzTR0IQkJCWLp0KaNGjWLOnDk4OTnh4eHB5s2bqays\n5Pbbb+e5557jhhtuoLq6mmXLlvH3v/+dH3/8kZtuuokpU6YwYkTtejcPPvggnTt3Nrsdf31t1/Le\nlmb3cwhEROT6Y09LzH/88cdUV1fz3nvvsXz5cnbu3ElKSgpQe3v9zz77jMTERLZt28bw4cN56qmn\nyM3NbfL3tjS7O0MgIiLi6+XLyZ9O1mm3xRLzbdq0Yf78+bRo0YKbbrqJ2267ja+//hqAH374gZYt\nW+Lv74+3tzdTpkyhZ8+etGnTpsnf29J0hkBEROyOPS0x37lzZ1q0aGF67OXlZbqybdy4cfz0008M\nGjSI6OhoEhMTCQgIMN0rpynf29IUCERExO708e/DpF6TCGgVgJPBiYBWAUzqNckmK8y6urrWaTMa\njQB07dqV7du388Ybb9CrVy+2bNnC73//e/75z39ecnvnz5+3yHtbmoYMRETELjnCEvPr16+nTZs2\n3HXXXQwaNIjZs2dz99138+mnn9K/f39cXFz4+eefTc+vqakhNzeXrl272rDq+ikQiIiIXKMff/yR\nxMREPDw86N69O99++y0nTpzg0UcfBWqvgnv77bf54osvCAwMZO3atWar9doTBQIREZFr9Pjjj1NR\nUcFzzz1HcXExvr6+TJ06lXvuuQeARx55hOPHjzNt2jRcXV0ZM2YMd911l42rrp/B2FSDEXbuxIkT\nDB06lB07dhAQEGDrckRERJrc5b77NKlQREREFAhEREREgUBERERQIBAREREUCERERAQFAhEREUGB\nQERERFAgEBERERQIREREBAUCERERQYFAREREUCAQERERFAhEREQEBQIRERFBgUBERERQIBAREREU\nCERERAQFAhEREUGBQERERLDDQLBw4ULmzZtn1rZ7925GjRpFz549GTlyJGlpaWb9p0+fZvr06URE\nRNC/f3/i4+M5f/68NcsWERFxaHYTCIxGIytWrOD99983a8/JySE2Npbhw4ezceNGhg4dSlxcHNnZ\n2abnTJ06leLiYlJTU1myZAkbNmwgMTHR2rsgIiLisOwiEOTm5vLQQw/x7rvv4ufnZ9aXkpJCWFgY\nsbGxBAUFMWPGDMLDw0lJSQEgKyuLPXv2sGTJEkJDQxk8eDCzZs1i3bp1VFZW2mJ3REREHI5dBIK9\ne/fi6+vL5s2bCQgIMOvLzMwkMjLSrK1v375kZmaa+v39/QkMDDT1R0ZGUlpayqFDh5q+eBERkWbA\n2dYFAIwaNYpRo0bV21dQUEDHjh3N2nx8fCgoKACgsLAQHx+fOv0A+fn53HrrrU1QsYiISPNiF2cI\nLqeiogJXV1ezNldXV86dOwdAeXk5bm5uZv0uLi4YDAbTc0REROTy7D4QuLm5UVVVZdZWWVlJy5Yt\nAXB3d68zV6Cqqgqj0YiHh4fV6hQREXFkdh8IfH19KSoqMmsrKioyDSN06tSJU6dO1ekH6gw1iIiI\nSP3sPhD07t2bjIwMs7b09HQiIiJM/bm5ueTn55v1e3p6EhoaatVaRUREHJXdB4IJEyaQmZnJypUr\nOXr0KCtWrGDfvn08/PDDAISHhxMWFsbMmTM5ePAgaWlpxMfHExMTU2fugYiIiNTP7gNBSEgISUlJ\nfPrpp4wePZqdO3eSnJxMUFAQAAaDgaSkJNq3b8/48eN59tlniY6OJi4uzsaVi4iIOA67uOzw19at\nW1enbciQIQwZMuSSr/H29ua1115rwqpERESaN7s/QyAiIiJNT4FAREREFAhEREREgUBERERQIBAR\nEREUCERERAQFAhEREUGBQERERFAgEBERERQIREREBAUCERERQYFAREREsMPFjRxdxskMtuZsJb8k\nH18vX0YEj6CPfx9blyUiInJZCgQWlHEyg9V7V5sen/zppOmxQoGIiNgzDRlY0NacrfW2b8vZZuVK\nREREGkaBwILyS/Lrbc8rybNyJSIiIg2jQGBBvl6+9bb7eflZuRIREZGGUSCwoBHBI+ptHx483MqV\niIiINIwmFVrQhYmD23K2kVeSh5+XH8ODh2tCoYiI2D2LBYKMjAy2b9/O3LlzLbVJh9THv48CgIiI\nOByLDRl8++23pKSkWGpzIiIiYkWaQyAiIiIKBCIiIqJAICIiIigQiIiICFdxlcEjjzxyVRvKy9Pd\n+ERERBzVFQNBVVXVVW3I29sbb2/vRhd0sfT0dB566KF6+/r27UtKSgpjxoxh//79Zn1jxozhxRdf\ntHg9IiIizdEVA8G6deusUcclhYeHs3v3brO2L7/8krlz5/LHP/4Ro9FITk4Oy5Yto1+/fqbntGzZ\n0tqlioiIOKwG3ZiosrKSd955h6ysLEpKSur0GwwG1qxZY7HiAFxdXc3OPJSUlLBs2TIeffRRBg4c\nyPHjxykvLycsLKxJzlCIiIhcDxoUCBYvXsyHH35It27daNOmTVPVdFmvv/46rq6uxMXFAXDkyBHc\n3d3x9/e3ST0iIiLNQYMCwWeffca0adOYMmVKU9VzWadPnyY1NZVFixaZhgSys7Px8vLi6aef5l//\n+hdt27bl3nvv5eGHH8bJSRdRiIiIXI0GBQKDwUBYWFhT1XJF7777Lu3bt+fuu+82teXk5FBWVkZU\nVBSTJ09m7969LF26lJKSEqZNm2azWkVERBxJgwLBPffcw4cffki/fv1s8tP3xx9/zL333ouLi4up\n7eWXX6asrIxWrVoBEBISQklJCcnJyUydOhWDwWD1OkVERBxNgwLB9OnTueeee7jjjju4+eab68zk\nNxgMvPTSSxYt8ILs7Gx++OEH7rrrLrN2Z2dnUxi4ICQkhNLSUkpKSur0iYiISF0NCgTLli3j2LFj\neHl58e2339bpb8qfxjMzM/H29iYoKMisfezYsfTs2ZP58+eb2vbv34+Pj4/CgIiIyFVqUCDYtGkT\nf/zjH3nyySetfir+0KFDdO/evU77sGHDWLlyJT169KBXr16kp6ezevVq5s2bZ9X6REREHFmDAkGL\nFi0YMGCATcbli4qKaN26dZ32SZMm4ezszKpVq8jLy8PPz4+5c+cSHR1t9RpFREQcVYMCwciRI02T\nCq0tOTm53naDwUBMTAwxMTFWrkhERKT5aFAgaN++PRs3bmTYsGHccssteHp6mvUbDAYWL15s0QJF\nRESk6TUoEHzwwQe0bt2a6upqvv766zr9usRPRETEMTUoEOzcubOp6hAREREbuuLdhdatW8fx48et\nUYuIiIjYyBXPEKSlpbFs2TJ8fHwYNGgQgwYNom/fvri7u1ujPhEREbGCKwaC1atXc+7cOb766iu+\n+OILXnzxRQoLC4mIiGDgwIEMHDiwzs2CRERExLFc1RwCNzc3Bg8ezODBgwH4/vvv+eKLL9i1axcJ\nCQm0b9+eQYMGMXDgQIYOHdqkBYuIiIjlNWhS4QU33ngjN954Iw8++CDnzp0jPT2dXbt2sXTpUgUC\nERERB3RNgeDX3NzcTHMLRERExDFdMRAkJSXV224wGPDw8KBDhw706dOHTp06Wbw4ERERsY4rBoJV\nq1Zdsq+6uhqoXePgkUce4amnnrJcZSIiImI1VwwEBw8evGRfTU0NhYWFfPrppyxbtoygoCBGjx5t\n0QJFRESk6V3xxkSXfbGTE76+vkycOJH777+fd99911J1iYiIiBU1KhD8Wr9+/Th27JilNiciIiJW\nZLFA0KpVK6qqqiy1OREREbEiiwWCQ4cO6UoDERERB2WRQHDw4EH+8pe/MGzYMEtsTkRERKzsilcZ\nPPLII5fsq6yspKioiNzcXH7zm98QGxtr0eJERETEOq4YCC41L8BgMHDDDTdw44038sQTT3DnnXfi\n7NzoGx+KiIiIDVzxG3zdunXWqENERERs6Kp/pJ82bRqhoaF0796dkJAQAgMDzfq/++47WrZsSefO\nnS1epIiIiDStqw4Ex48f5/PPP6eyshKDwYC7uzvdunUjJCSEbt26kZWVxf79+9m+fXtT1isiIiJN\n4KoDwaZNm6iurubYsWMcOXKE7777jsOHD7NlyxbKy8sB8PX1bbJCRUREpOk0aBZgixYtCA4OJjg4\nmDvvvBOovdLgzTffJCUlhTfeeKNJihQREZGm1ej7ELi6uhIXF0e/fv149dVXLVGTiIiIWJnF7lTY\nu3dvvvrqK0ttTkRERKzoqocMFixYYHaVQatWrcz6jx8/Tvv27S1eoIiIiDS9qw4EX3zxBR988AFQ\ne1Oijh07Ehoayk033cTp06f5f//v/7Fs2bImKTInJ4e77rqrTvv69euJiIhg9+7dxMfHc+zYMbp0\n6cLTTz/N4MGDm6SWa5FxMoOtOVvJL8nH18uXEcEj6OPfx9ZliYiImFx1IPj888/5+eefOXLkCNnZ\n2Rw5coQjR46wadMmfvzxRwDi4uLo0qULQUFBdO3aleDgYH7/+983usgjR47Qtm1bNm/ebNbepk0b\ncnJyiI2NZcqUKfzud79j8+bNxMXFsXHjRrp169bo926sjJMZrN672vT45E8nTY8VCkRExF406CqD\nG264gV69etGrVy+z9uLiYlNAuBAWvvjiCyoqKiwWCIKDg/H29q7Tl5KSQlhYmGkdhRkzZrBnzx5S\nUhrjo3oAABiRSURBVFJ4/vnnG/3ejbU1Z2u97dtytikQiIiI3bDI4gMdOnSgQ4cO3HbbbWbtubm5\nltg82dnZdO3atd6+zMxMRowYYdbWt29ftmzZYpH3bqz8kvw6bUWlRezN30teSZ6GEERExC5Y7CqD\n+lx8e+NrlZ2dTV5eHmPHjmXAgAFMnDiRb775BoCCggI6duxo9nwfHx8KCgos8t6N5etlfrOmotIi\nDp8+DECNscY0hJBxMsMW5YmIiABNHAgsoaKigtzcXH7++WdmzZrFqlWr8PHxYcKECRw9epSKigpc\nXV3NXuPq6sq5c+dsVLG5EcHmZy9yf6o9axLY2jwsbcvZZrWaRERELmb36xW7u7uTkZGBq6ur6Yt/\nyZIlHDx4kHfeeQc3N7c6SzRXVlbSsmVLW5Rbx4WhgG0528grycPJ4MRvOvwGbw/z+RB5JXm2KE9E\nRARwgEAAtZMZf83JyYng4GDy8/Px9fWlqKjIrL+oqKjOMIIt9fHvYwoGi9MWc/Knk3We4+flZ+2y\nRERETOx+yODAgQP06tWLAwcOmNqqq6s5fPgw3bp1o3fv3mRkmI+/p6enExERYe1Sr8rFQwgXDA8e\nbuVKREREfmH3ZwhCQ0Px9/dn4cKF/OlPf8LDw4M333yTH3/8kYceeoji4mLuu+8+Vq5cyf9v796D\nojrPMIA/rCsBCSihQleMSYSwtghyUcC6CpKaqBlqq0C8QAqtE3VSsCUaQ7lMYtKpQBQvDOhAaxsh\nrbGgxmgnzWgDTcdakEaFSgSnIoIKigLhKuzpHw6ryy4gsLtnz/L8ZpjR7+zlPbxnOe8532VfffVV\nfPbZZ7hw4QLeffddsUPXa2AXwjT7aZpiYHvxdi5eREREojD7gkAulyMvLw/p6enYuHEjOjs74efn\nh/z8fDg5OcHJyQlZWVnIyMhAbm4uZs6cif3798PNzU3s0Af1eBcCwMWLiIhIfGZfEACAi4sLdu7c\nOej2kJAQhISEmC4gA+PiRUREJDazH0MwHuhbvAjgzAMiIjIdFgRmYODiRf0484CIiEyFBYEZ4MwD\nIiISmyTGEFg6zjwgIiKxsSAwE5x5QEREYmKXgZkaauYBERGRobEgMFOceUBERKbEgsBMceYBERGZ\nEgsCM8WZB0REZEocVGimBpt5wAGFRERkDCwIzNjAmQdERETGwi4DIiIiYkFARERE7DKQnNL6Uvy1\n5q9cvZCIiAyKBYGEcPVCIiIyFnYZSAhXLyQiImNhQSAhXL2QiIiMhQWBhHD1QiIiMhYWBBLC1QuJ\niMhYOKhQQrh6IRERGQsLAokZavXCoaYkGnO6IqdCEhFJHwsCCzHUlEQARpuuyKmQRESWgWMILMRQ\nUxKNOV2RUyGJiCwD7xBYiNFMSTTEdEVOhSQisgy8Q2AhhpqSaMzpipwKSURkGVgQWIihpiSOZrpi\naX0pthdvx6bPNmF78XaU1peO+H2JiEg62GVgIZ5kSuKTTlccyUBBToUkIrIMkigI7ty5g4yMDPzz\nn/9EV1cX5syZg23btsHDwwMAEB4ejkuXLmk9Jzw8HL/5zW8MGoe5T68bakriUNsGGmqgoL7XGMlr\nExGReTL7gkCtVuMXv/gFBEFAdnY2Jk2ahH379iEmJgYnT57ElClTUFNTgw8//BBBQUGa59na2ho0\njvE0vY4DBYmIxh+zLwiqqqrwn//8B6dOnYKbmxsAICMjAwEBASguLoafnx86Ozvh4+ODqVOnGi2O\nkV41S5nCXoH61nqddg4UJCKyXGY/qFChUODAgQN44YUXNG1WVlYAgJaWFly5cgU2NjZwdXU1ahzj\n6aqZAwWJiMYfsy8IHB0dERISApnsUaiHDh1CV1cXVCoVqqurYW9vjy1btkClUiEsLAwHDx6EWq02\naBzjaXrdPNd5WO+3HtMdpkNmJcN0h+lY77fe4u6EEBHRI2bfZTDQ6dOnsWvXLsTGxsLNzQ01NTXo\n6OiASqXChg0bUF5ejvT0dLS1tSE+Pt5g77vMfZnWGIJ+lnrVzIGCRETji6QKgqKiIqSkpGD58uXY\nunUrACAtLQ0dHR1wcHAAACiVSrS1tWH//v2Ii4vTdC+MFafXicfcZ3cQEVkCyRQEOTk52L17N6Ki\nopCcnKw50cvlck0x0E+pVKK9vR1tbW0628aCV82mN55mdxARicnsxxAAQG5uLnbv3o34+HikpKRo\nXfVHRkbigw8+0Hr8pUuX4OzsbNBigMTBL08iIjINs79DUFVVhczMTKxatQqRkZFoamrSbLOzs8OS\nJUuwd+9ezJ49G35+fjh37hzy8vKQlJQkYtRkKONpdgcRkZjMviA4deoU+vr6UFhYiMLCQq1tmzdv\nxqZNmyCXy5GTk4OGhgZMmzYNiYmJiIiIECliMiSuiUBEZBpmXxAkJCQgISFhyMfExsYiNjbWRBGR\nKY232R1ERGIx+4KAxjfO7iAiMg0WBGT2OLuDiMj4JDHLgIiIiIyLBQERERGxICAiIiKOISAy6tLI\nXHaZiKSCBQGNa8ZcGpnLLhORlLDLgMY1Yy6NzGWXiUhKWBDQuGbMpZG57DIRSQkLAhrXFPYKve2G\nWBrZmK9NRGRoLAhoXFvmvkxvuyGWRjbmaxMRGRoHFQ6Co8PHB2MujWyKZZd5nOrH3wvRyLEg0IOj\nw8cXYy6NbMzXNvZxKtWTKj+/RKPDLgM9ODqcpMCYx2n/SbW+tR5qQa05qZbWl475tY2Nn1+i0WFB\noAdHh5MUGPM4lfJJlZ9fotFhl4EeCnsF6lvrddo5OpzMiTGPU1OcVI3VJcHPr7ik2tUESDt2Q+Ad\nAj04OpykwJjHqbGnTBqzS4KfX/FIuatJyrEbCu8Q6GGK0eFEY2XM43SZ+zKtgXn9DHVSHapLYqzx\nW8LnV6pXqsbMq7FJOXbAMMcMC4JBGHN0OJGhGOs4NfZJ1dhdElL+/Ep5loSUx29IOXZDHTMsCIhI\nL2OeVNnPPzgpX6lKOa9Sjt1QxwzHEBCRybGff3BSvlKVcl6lHLuhjhneISAik7OEfn5jkfKVqpTz\nKuXYDXXMsCAgIlFIuZ/fmIw9oNPYpJxXqcZuqGNm3BYEfX19AIBbt26JHAkR0SMKKLBi2goUXyvG\n7fbbcLFzQfDzwVAICty4cUPs8MgMjeSY6T/n9Z8DHzduC4KmpiYAwLp160SOhIhoaMdwTOwQSGKG\nO2aamprw3HPPabVZCYIgGDMoc9XV1YWKigpMnToVEyZMEDscIiIio+vr60NTUxNmz54NGxsbrW3j\ntiAgIiKiRzjtkIiIiFgQEBEREQsCIiIiAgsCIiIiAgsCIiIiAgsCvfr6+rBz506oVCr4+voiPj4e\nd+7cETssg7hz5w62bdsGlUqFuXPn4uc//zmuXLmi2R4eHg6lUqn1k5SUJGLEo1dTU6OzL0qlEmVl\nZQCAr776CitWrIC3tzfCwsJQXFwscsSjc+7cOb37qVQq8frrrwOwnLympqbqxD1cHu/evYvNmzdj\n7ty5mD9/PjIyMtDb22vKsEdF377m5+dj6dKl8PHxwfLly3HkyBGt7QUFBTp5/v73v2/KsEdF374O\nd8xaSl5DQ0MH/fw2NDz8LgKT5VUgHZmZmcKCBQuEr776SqioqBAiIiKE1atXix3WmPX19Qmvvfaa\nEBkZKVy4cEGorq4W4uPjhfnz5wvNzc2CWq0W5syZI3z66adCY2Oj5qetrU3s0Efl5MmTQmBgoNa+\nNDY2Cj09PUJ1dbUwe/ZsITs7W6ipqREyMzMFT09P4cqVK2KHPWLd3d06+3j06FFh1qxZQklJiUXk\nVa1WC7t37xY8PDyEX//615r2J8njmjVrhLVr1wqXL18WvvzySyEoKEjYtWuXGLvxRAbb14KCAsHH\nx0c4duyYUFtbK3zyySeCp6encPToUc1jUlNThY0bN2rluampSYzdeCKD7euTHLOWkte7d+9q7WNt\nba0QHBwsvPXWW5rHmCqvLAgG6O7uFnx9fYXCwkJNW11dneDh4SGcP39exMjGrrKyUvDw8BBqamo0\nbd3d3cKcOXOEo0ePCrW1tYKHh4dw/fp1EaM0nMzMTGHdunV6t6WkpAhRUVFabVFRUUJycrIpQjOq\n1tZWYcGCBUJGRoYgCILk83r9+nUhKipKCAwMFEJCQrT+mA6Xx/Lycp19LyoqEnx9fYXu7m7T7MAI\nDLWvYWFhQnp6utbjExMThejoaM3/16xZI+zZs8dk8Y7FUPs63DFrSXkdKDU1VQgNDRU6Ojo0babK\nK7sMBqiqqkJ7ezsCAgI0bdOnT4erq6vmVrNUKRQKHDhwAC+88IKmzcrKCgDQ0tKCK1euwMbGBq6u\nrmKFaFDV1dWYOXOm3m1lZWVaOQaAwMBAyecYALKzs2FtbY0333wTACSf1/LycigUCpw4cQLTp0/X\n2jZcHsvKyuDq6opnn31Wsz0gIADt7e24fPmy8YMfoaH2NTk5GatXr9Zqk8lkaG1t1fy/pqYGbm5u\nJol1rIba1+GOWUvK6+OqqqrwySefIDU1Fba2tpp2U+WVBcEA/V/84OLiotXu7Ows+S9CcnR0REhI\nCGSyR2k/dOgQurq6oFKpUF1dDXt7e2zZsgUqlQphYWE4ePAg1Gq1iFGPXnV1NRoaGhAZGYkFCxYg\nJiYGFy9eBPAwz5aY47t37yI/Px9vvvmm5g+K1PO6YsUKpKenY+rUqTrbhsvj7du34ezsrLMdAG7e\n1P8d8mIaal8DAgK0ToANDQ04efIkFi5cCODhvra0tKCkpARLly5FcHAwtmzZgtu3b5ss/pEYal+H\nO2YtKa+P27dvH/z9/REcHKxpM2VeWRAM0NnZCZlMhokTJ2q1W1tbo7u7W6SojOP06dPYtWsXYmNj\n4ebmhpqaGnR0dEClUuF3v/sd1q5di7179yIrK0vsUEesq6sLdXV1+Pbbb/H2228jJycHzs7OiIqK\nwtWrV9HV1QVra2ut51hCjv/0pz/ByckJP/rRjzRtlpTXgYbLY2dnJ5566imt7RMnToSVlZWkc93c\n3IwNGzbgO9/5Dt544w0AD0+iACCXy5GZmYnf/va3uHbtGmJiYtDV1SVmuCM23DFriXmtq6vDmTNn\nsGHDBq12U+Z13H7b4WBsbGygVqvR29sLufzRr6enp0frFo7UFRUVISUlBcuXL8fWrVsBAGlpaejo\n6ICDgwMAQKlUoq2tDfv370dcXJyme0EKbGxsUFpaCmtra80JY8eOHaisrMTHH3+Mp556Cg8ePNB6\njiXk+NNPP8XKlSu1ClpLyutAw+XRxsYGPT09WtsfPHgAQRAwadIkk8VpSHV1dVi/fj26urqQn58P\ne3t7AIBKpcLZs2fxzDPPaB7r7u6ORYsWobi4GK+88opYIY/YcMesJeb1xIkTUCgUUKlUWu2mzCvv\nEAygUCgAPPp65H6NjY06tyalKicnB4mJiVi9ejXS09M1XQhyuVzzAeynVCrR3t6OtrY2MUIdk6ef\nflrr6lEmk8Hd3R03b96EQqFAY2Oj1uOlnuPq6mrU1tbi1Vdf1Wq3tLw+brg8fve739X7WQZ0uwWl\noLKyEq+99hpkMhn+/Oc/a3UhANA6aQAPb6M7Ojqa5W30oQx3zFpaXoGHd2yXLVumt0A3VV5ZEAww\na9Ys2NnZ4d///rem7caNG6ivr8e8efNEjMwwcnNzsXv3bsTHxyMlJUXr4IuMjMQHH3yg9fhLly7B\n2dlZ58Np7ioqKuDn54eKigpNW19fH6qqqvDiiy/C398fpaWlWs85d+4c5s6da+pQDaasrAxTp07V\nGXxkSXkdaLg8+vv7o66uTusP57lz52BnZ4dZs2aZNNaxunr1Kn72s5/B1dUVH3/8sebipd9HH30E\nlUqldcekvr4ezc3NePHFF00d7pgMd8xaUl4BoKOjA5cvX0ZQUJDONlPmlQXBANbW1li7di3S09NR\nUlKCyspKJCQkICAgAD4+PmKHNyZVVVXIzMzEqlWrEBkZiaamJs1PR0cHlixZgsOHD+PYsWO4fv06\njhw5gry8PMTHx4sd+ojNmjULrq6uSE1NxYULF1BdXY3ExETcu3cPr7/+OqKiolBWVoa9e/fi6tWr\n2LNnDy5cuICf/vSnYoc+apcvX4aHh4dOuyXldaDh8ujr6wsfHx/86le/QmVlJYqLi5GRkYHY2Fid\nsQfmbtu2bbC2tkZ6ejp6e3s1n93m5mYAQEhICNrb25GUlISrV6/i/PnziIuLg7+/PxYsWCBy9CMz\n3DFrSXkFgG+++QZ9fX16P7+mzCvHEOjxy1/+Er29vdi6dSt6e3uxcOFCpKamih3WmJ06dQp9fX0o\nLCxEYWGh1rbNmzdj06ZNkMvlyMnJQUNDA6ZNm4bExERERESIFPHoyeVy5OXlIT09HRs3bkRnZyf8\n/PyQn58PJycnODk5ISsrCxkZGcjNzcXMmTOxf/9+yUzZ0qexsRGTJ0/WaV+/fr3F5HUgpVI5ZB6t\nrKyQlZWFd999F+vWrYOdnR0iIiI0UzKl4n//+x8uXboEAFi6dKnWthkzZuCLL77AjBkzcPDgQezc\nuRMRERGYOHEiQkND8c4774gR8pgMd8xaSl779Xd/TJkyRWebKfNqJQiCYPBXJSIiIklhlwERERGx\nICAiIiIWBERERAQWBERERAQWBERERAQWBERERAQWBEQW65133oFSqRzyJzo6GgAQHR2NmJgYUeO9\nf/8+QkNDUVtbO+rXuHHjBpRKJY4fP/7Ez2lpaUFoaCjq6upG/b5EloDrEBBZqOvXr2tWsQOA9957\nDxMmTEBycrKm7emnn4a7uztqampgZWUl6sJMb731FlxcXPD222+P+jV6enrw3//+FzNmzNBZ/30o\n+fn5+Pzzz/HRRx9J+sueiMaCBQHROBEdHY0JEybgD3/4g9ih6Lh48SLWrl2LkpKSEZ3IDaWnpwfB\nwcF477338PLLL5v8/YnMAbsMiEiny0CpVOLw4cPYsmULfH19ERQUhKysLHz77bdITEzUrKOekZGB\nx68p7t27h+TkZMyfPx/e3t5Ys2YNzp8/P+z75+Xl4Qc/+IFWMRAaGors7Gy8//77CAgIgL+/P7Zv\n347Ozk6kpaUhMDAQgYGBSEpKQnd3NwDdLoOioiJ4eXmhvLwcERER8PLywuLFi/H73/9e6/2tra3x\n8ssv48CBA2P5NRJJGgsCItIrLS0Njo6OyM7OxuLFi7Fv3z6Eh4fD1tYWWVlZWLJkCfLy8vC3v/0N\nANDd3Y2YmBh8+eWXSEhIwN69ezF58mTExMTg4sWLg75Pe3s7zpw5o/fKPC8vD/fv38eePXuwevVq\nFBQU4Cc/+Qlu3ryJnTt3Ijo6Gn/5y19QUFAw6Ov39vYiISEBYWFhyM3NhZ+fH9LS0nD27Fmtxy1d\nuhQVFRW4du3a6H5hRBLHLzciIr08PT2RlJQE4OG3RxYVFcHJyUnzRV9BQUE4ceIEvv76a7zyyis4\nfvw4vvnmGxw5cgReXl4AgEWLFiE8PByZmZk4ePCg3vcpKyvDgwcP4O3trbPN0dERGRkZkMlkCAwM\nxOHDh/HgwQN8+OGHkMvlUKlU+Pzzz/H1118Puh9qtRpxcXFYtWoVAMDPzw9ffPEF/v73v2P+/Pma\nx82ePRvAw6/Rff7550f+CyOSON4hICK9Hj9BOzo6YsKECVptVlZWmDx5MlpbWwEAZ8+ehYuLC773\nve+ht7cXvb29UKvVWLx4MUpLS9HT06P3fW7cuAEAmD59us42Ly8vyGQP/0zJZDI4OjrC09MTcvmj\na5kpU6ZoYhiMn5+f5t/W1tZ45pln0NnZqfUYe3t7ODg4oL6+fsjXIrJUvENARHrZ2dnptE2aNGnQ\nx9+/fx+3bt2Cp6en3u337t2Di4uLTntbWxsAwNbWdswxDGbga8tkMqjVar2P64+HaLxhQUBEBmFv\nbw83NzekpaXp3e7o6Dhke1tbGxwcHIwW35NobW0dNE4iS8cuAyIyiHnz5qGhoQHOzs7w8vLS/Jw+\nfRqHDh3CxIkT9T5v2rRpAIBbt26ZMlwdLS0t6OzshEKhEDUOIrGwICAig1i5ciVcXFwQGxuL48eP\n41//+hd27NiBnJwcPPvss4Mu+DN37lzY2Ng80fREYyovLwcAqFQqUeMgEgsLAiIyCDs7OxQUFGDO\nnDnYsWMH3njjDfzjH/9ASkoK4uLiBn2era0tFi1ahJKSEhNGq6ukpATe3t68Q0DjFlcqJCLRXbx4\nEWvWrMGZM2f0Djw0ts7OTixcuBA7duzAD3/4Q5O/P5E54B0CIhKdt7c3XnrpJZ0VBE3l8OHDcHd3\nx0svvSTK+xOZA94hICKz0NzcjJUrV+KPf/wjnnvuOZO97/379/HjH//Y5O9LZG5YEBARERG7DIiI\niIgFAREREYEFAREREYEFAREREYEFAREREQH4P03Ltvy5AvD7AAAAAElFTkSuQmCC\n", "text/plain": [ - "" + "" ] }, "metadata": {}, diff --git a/code/chap09-fig01.pdf b/code/chap09-fig01.pdf new file mode 100644 index 00000000..c48a7737 Binary files /dev/null and b/code/chap09-fig01.pdf differ diff --git a/code/chap09-fig02.pdf b/code/chap09-fig02.pdf new file mode 100644 index 00000000..f864aafe Binary files /dev/null and b/code/chap09-fig02.pdf differ diff --git a/code/chap09mine.ipynb b/code/chap09mine.ipynb new file mode 100644 index 00000000..0eccd33b --- /dev/null +++ b/code/chap09mine.ipynb @@ -0,0 +1,7346 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Modeling and Simulation in Python\n", + "\n", + "Chapter 9: Projectiles\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": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# If you want the figures to appear in the notebook, \n", + "# and you want to interact with them, use\n", + "# %matplotlib notebook\n", + "\n", + "# If you want the figures to appear in the notebook, \n", + "# and you don't want to interact with them, use\n", + "# %matplotlib inline\n", + "\n", + "# If you want the figures to appear in separate windows, use\n", + "# %matplotlib qt5\n", + "\n", + "# tempo switch from one to another, you have to select Kernel->Restart\n", + "\n", + "%matplotlib notebook\n", + "\n", + "from modsim import *" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "### Dropping pennies\n", + "\n", + "I'll start by getting the units we'll need from Pint." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "m = UNITS.meter\n", + "s = UNITS.second\n", + "kg = UNITS.kilogram" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "And defining the initial state." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
y381 meter
v0.0 meter / second
\n", + "
" + ], + "text/plain": [ + "y 381 meter\n", + "v 0.0 meter / second\n", + "dtype: object" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "init = State(y=381 * m, \n", + " v=0 * m/s)\n", + "init" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Acceleration due to gravity is about 9.8 m / s$^2$." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "g = 9.8 * m/s**2" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "When we call `odeint`, we need an array of timestamps where we want to compute the solution.\n", + "\n", + "I'll start with a duration of 10 seconds." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "[ 0. 0.52631579 1.05263158 1.57894737 2.10526316 2.63157895 3.15789474 3.68421053 4.21052632 4.73684211 5.26315789 5.78947368 6.31578947 6.84210526 7.36842105 7.89473684 8.42105263 8.94736842 9.47368421 10. ] second" + ], + "text/latex": [ + "$[ 0. 0.52631579 1.05263158 1.57894737 2.10526316 2.63157895 3.15789474 3.68421053 4.21052632 4.73684211 5.26315789 5.78947368 6.31578947 6.84210526 7.36842105 7.89473684 8.42105263 8.94736842 9.47368421 10. ] second$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "duration = 10 * s\n", + "ts = linspace(0, duration, 20)\n", + "ts" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Now we make a `System` object." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "system = System(init=init, g=g, ts=ts)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "And define the slope function." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def slope_func(state, t, system):\n", + " \"\"\"Compute derivatives of the state.\n", + " \n", + " state: position, velocity\n", + " t: time\n", + " system: System object containing `g`\n", + " \n", + " returns: derivatives of y and v\n", + " \"\"\"\n", + " #print(t)\n", + " y, v = state\n", + " unpack(system) \n", + "\n", + " dydt = v\n", + " dvdt = -g\n", + " \n", + " return dydt, dvdt" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "It's always a good idea to test the slope function with the initial conditions." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.0 meter / second\n", + "-9.8 meter / second ** 2\n" + ] + } + ], + "source": [ + "\n", + "dydt, dvdt = slope_func(init, 0, system)\n", + "print(dydt)\n", + "print(dvdt)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Now we're ready to run `odeint`" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "run_odeint(system, slope_func)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Here's what the results look like." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
yv
0.000000381.0000000.000000
0.526316379.642659-5.157895
1.052632375.570637-10.315789
1.578947368.783934-15.473684
2.105263359.282548-20.631579
\n", + "
" + ], + "text/plain": [ + " y v\n", + "0.000000 381.000000 0.000000\n", + "0.526316 379.642659 -5.157895\n", + "1.052632 375.570637 -10.315789\n", + "1.578947 368.783934 -15.473684\n", + "2.105263 359.282548 -20.631579" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "system.results.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
yv
7.89473775.598338-77.368421
8.42105333.520776-82.526316
8.947368-11.271468-87.684211
9.473684-58.778393-92.842105
10.000000-109.000000-98.000000
\n", + "
" + ], + "text/plain": [ + " y v\n", + "7.894737 75.598338 -77.368421\n", + "8.421053 33.520776 -82.526316\n", + "8.947368 -11.271468 -87.684211\n", + "9.473684 -58.778393 -92.842105\n", + "10.000000 -109.000000 -98.000000" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "system.results.tail()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "The following function plots the results." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def plot_position(results):\n", + " \"\"\"Plot the results.\n", + " \n", + " results: DataFrame with position, `y`\n", + " \"\"\"\n", + " newfig()\n", + " plot(results.y, label='y')\n", + " \n", + " decorate(xlabel='Time (s)',\n", + " ylabel='Position (m)')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Here's what it looks like." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ts2 = SweepSeries()\n", + "for i in linrange(0,25,1):\n", + " system = make_system(10,i)\n", + " result = flight_time(system)\n", + " ts2[int(i)] = result\n", + " print(result)\n", + "newfig()\n", + "plot(ts2)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "#inverse function error" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### With air resistance" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we'll add air resistance using the [drag equation](https://en.wikipedia.org/wiki/Drag_equation)\n", + "\n", + "First I'll create a `Condition` object to contain the quantities we'll need." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "condition = Condition(height = 381 * m,\n", + " v_init = 0 * m / s,\n", + " g = 9.8 * m/s**2,\n", + " mass = 2.5e-3 * kg,\n", + " diameter = 19e-3 * m,\n", + " rho = 1.2 * kg/m**3,\n", + " v_term = 18 * m / s,\n", + " duration = 30 * s)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Now here's a version of `make_system` that takes a `Condition` object as a parameter.\n", + "\n", + "`make_system` uses the given value of `v_term` to compute the drag coefficient `C_d`." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def make_system(condition):\n", + " \"\"\"Makes a System object for the given conditions.\n", + " \n", + " condition: Condition with height, g, mass, diameter, \n", + " rho, v_term, and duration\n", + " \n", + " returns: System with init, g, mass, rho, C_d, area, and ts\n", + " \"\"\"\n", + " unpack(condition)\n", + " \n", + " init = State(y=height, v=v_init)\n", + " area = np.pi * (diameter/2)**2\n", + " C_d = 2 * mass * g / (rho * area * v_term**2)\n", + " ts = linspace(0, duration, 101)\n", + " \n", + " return System(init=init, g=g, mass=mass, rho=rho,\n", + " C_d=C_d, area=area, ts=ts)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Let's make a `System`" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
inity 381 meter\n", + "v 0.0 meter / secon...
g9.8 meter / second ** 2
mass0.0025 kilogram
rho1.2 kilogram / meter ** 3
C_d0.4445009981135434 dimensionless
area0.0002835287369864788 meter ** 2
ts[0.0 second, 0.3 second, 0.6 second, 0.8999999...
\n", + "
" + ], + "text/plain": [ + "init y 381 meter\n", + "v 0.0 meter / secon...\n", + "g 9.8 meter / second ** 2\n", + "mass 0.0025 kilogram\n", + "rho 1.2 kilogram / meter ** 3\n", + "C_d 0.4445009981135434 dimensionless\n", + "area 0.0002835287369864788 meter ** 2\n", + "ts [0.0 second, 0.3 second, 0.6 second, 0.8999999...\n", + "dtype: object" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "system = make_system(condition)\n", + "system" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Here's the slope function, including acceleration due to gravity and drag." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def slope_func(state, t, system):\n", + " \"\"\"Compute derivatives of the state.\n", + " \n", + " state: position, velocity\n", + " t: time\n", + " system: System object containing g, rho,\n", + " C_d, area, and mass\n", + " \n", + " returns: derivatives of y and v\n", + " \"\"\"\n", + " y, v = state\n", + " unpack(system)\n", + " \n", + " f_drag = rho * v**2 * C_d * area / 2\n", + " a_drag = f_drag / mass\n", + " \n", + " dydt = v\n", + " dvdt = -g + a_drag\n", + " \n", + " return dydt, dvdt" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "As always, let's test the slope function with the initial conditions." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(, )" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "slope_func(system.init, 0, system)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "And then run the simulation." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "run_odeint(system, slope_func)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "First check that the simulation ran long enough for the penny to land." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(,\n", + " )" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "final_state(system.results)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Then compute the flight time." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array(22.439794207078908)" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "y = system.results.y\n", + "inverse = Series(y.index, index=y.values)\n", + "T = interpolate(inverse, kind='cubic')\n", + "T_sidewalk = T(0)\n", + "T_sidewalk" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Setting the duration to the computed flight time, we can check the final conditions." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "condition.set(duration=T_sidewalk)\n", + "system = make_system(condition)\n", + "run_odeint(system, slope_func)\n", + "y_final, v_final = final_state(system.results)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "The final height is close to 0, as expected. And the final velocity is close to the given terminal velocity." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(,\n", + " )" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "y_final, v_final" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Here's the plot of position as a function of time." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_velocity(system.results)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "From an initial velocity of 0, the penny accelerates downward until it reaches terminal velocity; after that, velocity is constant." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Run the simulation with an initial velocity, downward, that exceeds the penny's terminal velocity. Hint: use `condition.set`.\n", + "\n", + "What do you expect to happen? Plot velocity and position as a function of time, and see if they are consistent with your prediction." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "condition.set(v_init=-50 * m / s,)\n", + "system = make_system(condition)\n", + "run_odeint(system, slope_func)\n", + "plot_position(system.results)\n", + "plot_velocity(system.results)" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": { + "collapsed": true, + "scrolled": false + }, + "outputs": [], + "source": [ + "#gets slower than stays constant" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Dropping quarters" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Suppose we drop a quarter from the Empire State Building and find that its flight time is 19.1 seconds. We can use this measurement to estimate the coefficient of drag.\n", + "\n", + "Here's a `Condition` object with the relevant parameters from\n", + "https://en.wikipedia.org/wiki/Quarter_(United_States_coin)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "condition = Condition(height = 381 * m,\n", + " v_init = 0 * m / s,\n", + " g = 9.8 * m/s**2,\n", + " mass = 5.67e-3 * kg,\n", + " diameter = 24.26e-3 * m,\n", + " rho = 1.2 * kg/m**3,\n", + " duration = 19.1 * s)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And here's a modified version of `make_system`" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def make_system(condition):\n", + " \"\"\"Makes a System object for the given conditions.\n", + " \n", + " condition: Condition with height, v_init, g, mass, diameter, \n", + " rho, C_d, and duration\n", + " \n", + " returns: System with init, g, mass, rho, C_d, area, and ts\n", + " \"\"\"\n", + " unpack(condition)\n", + " \n", + " init = State(y=height, v=v_init)\n", + " area = np.pi * (diameter/2)**2\n", + " ts = linspace(0, duration, 101)\n", + " \n", + " return System(init=init, g=g, mass=mass, rho=rho,\n", + " C_d=C_d, area=area, ts=ts)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can run the simulation with an initial guess of `C_d=0.4`." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving figure to file chap10-fig01.pdf\n" + ] + } + ], + "source": [ + "newfig()\n", + "plot(xs, label='x')\n", + "plot(ys, label='y')\n", + "\n", + "decorate(xlabel='Time (s)',\n", + " ylabel='Position (m)')\n", + "\n", + "savefig('chap10-fig01.pdf')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can plot the velocities the same way." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "vxs = system.results.vx\n", + "vys = system.results.vy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The x velocity slows down due to drag. The y velocity drops quickly while drag and gravity are in the same direction, then more slowly after the ball starts to fall." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving figure to file chap10-fig02.pdf\n" + ] + } + ], + "source": [ + "newfig()\n", + "plot(xs, ys, label='trajectory')\n", + "\n", + "decorate(xlabel='x position (m)',\n", + " ylabel='y position (m)')\n", + "\n", + "savefig('chap10-fig02.pdf')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also animate the flight of the ball. If there's an error in the simulation, we can sometimes spot it by looking at animations." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "animate2d(system.results.x, system.results.y)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** Run the simulation for a few different launch angles and visualize the results. Are they consistent with your expectations?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Finding the range" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we'll find the time and distance when the ball hits the ground." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "condition.set(duration=7*s)\n", + "system = make_system(condition)\n", + "run_odeint(system, slope_func)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have to interpolate y to find the landing time, then interpolate x to find the range." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def interpolate_range(results):\n", + " \"\"\"Computes the range of the ball when it lands.\n", + " \n", + " results: TimeFrame with x and y\n", + " \n", + " returns: distance in meters\n", + " \"\"\"\n", + " xs = results.x\n", + " ys = results.y\n", + " t_end = ys.index[-1]\n", + " \n", + " if ys[t_end] > 0:\n", + " msg = \"\"\"The final value of y is still positive;\n", + " looks like the simulation didn't run\n", + " long enough.\"\"\"\n", + " raise ValueError(msg)\n", + " \n", + " t_peak = ys.argmax()\n", + " descent = ys.loc[t_peak:]\n", + " T = interp_inverse(descent, kind='cubic')\n", + " \n", + " t_land = T(0)\n", + " X = interpolate(xs, kind='cubic')\n", + " return X(t_land)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's the result." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array(31019.78312567125)" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interpolate_range(system.results)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Exercise:** The baseball stadium in Denver, Colorado is 1,580 meters above sea level, where the density of air is about 1.0 kg / meter$^3$. How much farther would a ball hit with the same velocity and launch angle travel?" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "condition2 = Condition(condition)\n", + "condition2.set(rho = 1*kg/(m**3))" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array(32866.539049926454)" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "system = make_system(condition2)\n", + "run_odeint(system, slope_func)\n", + "interpolate_range(system.results)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Optimal launch angle\n", + "\n", + "To find the launch angle that maximizes range, we need a function that takes launch angle and returns range." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def range_func(angle, condition): \n", + " \"\"\"Computes range for a given launch angle.\n", + " \n", + " angle: launch angle in degrees\n", + " condition: Condition object\n", + " \n", + " returns: distance in meters\n", + " \"\"\"\n", + " condition.set(angle=angle)\n", + " system = make_system(condition)\n", + " run_odeint(system, slope_func)\n", + " x_range = interpolate_range(system.results)\n", + " print(angle)\n", + " return x_range" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's test `range_func`." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "45\n", + "Wall time: 400 ms\n" + ] + }, + { + "data": { + "text/plain": [ + "array(32721.63619101987)" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%time range_func(45, condition)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And sweep through a range of angles." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "30.0\n", + "30.0 30667.53843861518\n", + "33.0\n", + "33.0 31683.220215036617\n", + "36.0\n", + "36.0 32391.60144005214\n", + "39.0\n", + "39.0 32797.576943830216\n", + "42.0\n", + "42.0 32905.986486316826\n", + "45.0\n", + "45.0 32721.63619101987\n", + "48.0\n", + "48.0 32249.37342689747\n", + "51.0\n", + "51.0 31494.206129460763\n", + "54.0\n", + "54.0 30461.448852109173\n", + "57.0\n", + "57.0 29156.909377944547\n", + "60.0\n", + "60.0 27587.109056842066\n" + ] + } + ], + "source": [ + "angles = linspace(30, 60, 11)\n", + "sweep = SweepSeries()\n", + "\n", + "for angle in angles:\n", + " x_range = range_func(angle, condition)\n", + " print(angle, x_range)\n", + " sweep[angle] = x_range" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plotting the `Sweep` object, it looks like the peak is between 40 and 45 degrees." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_results(system, title='Proportional growth model')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook demonstrates the steps we recommend for starting your project:\n", + "\n", + "1. Start with one of the examples from the book, either by copying a notebook or pasting code into a new notebook. Get the code working before you make any changes.\n", + "\n", + "2. Make one small change, and run the code again.\n", + "\n", + "3. Repeat step 2 until you have a basic implementation of your model.\n", + "\n", + "If you start with working code that you understand and make small changes, you can avoid spending a lot of time debugging.\n", + "\n", + "One you have a basic model working, you can think about what metrics to measure, what parameters to sweep, and how to use the model to predict, explain, or design." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Bonus question\n", + "\n", + "Suppose you only have room for 30 adult rabbits. Whenever the adult population exceeds 30, you take any excess rabbits to market (as pets for kind children, of course). Modify `run_simulation` to model this strategy. What effect does it have on the behavior of the system? You might have to run for more than 10 seasons to see what happens." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/code/rabbits3mine.ipynb b/code/rabbits3mine.ipynb new file mode 100644 index 00000000..9db8e192 --- /dev/null +++ b/code/rabbits3mine.ipynb @@ -0,0 +1,1509 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Modeling and Simulation in Python\n", + "\n", + "Rabbit example\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": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "\n", + "from modsim import *" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Rabbit is Rich\n", + "\n", + "This notebook starts with a version of the rabbit population growth model. You will modify it using some of the tools in Chapter 5. Before you attempt this diagnostic, you should have a good understanding of State objects, as presented in Section 5.4. And you should understand the version of `run_simulation` in Section 5.7.\n", + "\n", + "### Separating the `State` from the `System`\n", + "\n", + "Here's the `System` object from the previous diagnostic. Notice that it includes system parameters, which don't change while the simulation is running, and population variables, which do. We're going to improve that by pulling the population variables into a `State` object." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
t00.00
t_end20.00
juvenile_pop00.00
adult_pop010.00
birth_rate0.90
mature_rate0.33
death_rate0.50
\n", + "
" + ], + "text/plain": [ + "t0 0.00\n", + "t_end 20.00\n", + "juvenile_pop0 0.00\n", + "adult_pop0 10.00\n", + "birth_rate 0.90\n", + "mature_rate 0.33\n", + "death_rate 0.50\n", + "dtype: float64" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "system = System(t0 = 0, \n", + " t_end = 20,\n", + " juvenile_pop0 = 0,\n", + " adult_pop0 = 10,\n", + " birth_rate = 0.9,\n", + " mature_rate = 0.33,\n", + " death_rate = 0.5)\n", + "\n", + "system" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the following cells, define a `State` object named `init` that contains two state variables, `juveniles` and `adults`, with initial values `0` and `10`. Make a version of the `System` object that does NOT contain `juvenile_pop0` and `adult_pop0`, but DOES contain `init`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "init = State(juveniles = 0 , adults = 10)\n", + "\n", + "system = System(init = init,\n", + " t0 = 0, \n", + " t_end = 20,\n", + " birth_rate = 0.9,\n", + " mature_rate = 0.33,\n", + " death_rate = 0.5)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
initjuveniles 0\n", + "adults 10\n", + "dtype: int64
t00
t_end20
birth_rate0.9
mature_rate0.33
death_rate0.5
\n", + "
" + ], + "text/plain": [ + "init juveniles 0\n", + "adults 10\n", + "dtype: int64\n", + "t0 0\n", + "t_end 20\n", + "birth_rate 0.9\n", + "mature_rate 0.33\n", + "death_rate 0.5\n", + "dtype: object" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "system" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Updating `run_simulation`\n", + "\n", + "Here's the version of `run_simulation` from last time:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def run_simulation(system):\n", + " \"\"\"Runs a proportional growth model.\n", + " \n", + " Adds TimeSeries to `system` as `results`.\n", + " \n", + " system: System object\n", + " \"\"\"\n", + " juveniles = TimeSeries()\n", + " juveniles[system.t0] = system.juvenile_pop0\n", + " \n", + " adults = TimeSeries()\n", + " adults[system.t0] = system.adult_pop0\n", + " \n", + " for t in linrange(system.t0, system.t_end):\n", + " maturations = system.mature_rate * juveniles[t]\n", + " births = system.birth_rate * adults[t]\n", + " deaths = system.death_rate * adults[t]\n", + " \n", + " if adults[t] > 30:\n", + " market = adults[t] - 30\n", + " else:\n", + " market = 0\n", + " \n", + " juveniles[t+1] = juveniles[t] + births - maturations\n", + " adults[t+1] = adults[t] + maturations - deaths - market\n", + " \n", + " system.adults = adults\n", + " system.juveniles = juveniles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the cell below, write a version of `run_simulation` that works with the new `System` object (the one that contains a `State` object named `init`).\n", + "\n", + "Hint: you only have to change two lines." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def run_simulation(system):\n", + " \"\"\"Runs a proportional growth model.\n", + " \n", + " Adds TimeSeries to `system` as `results`.\n", + " \n", + " system: System object\n", + " \"\"\"\n", + " juveniles = TimeSeries()\n", + " juveniles[system.t0] = system.init.juveniles\n", + " \n", + " adults = TimeSeries()\n", + " adults[system.t0] = system.init.adults\n", + " \n", + " for t in linrange(system.t0, system.t_end):\n", + " maturations = system.mature_rate * juveniles[t]\n", + " births = system.birth_rate * adults[t]\n", + " deaths = system.death_rate * adults[t]\n", + " \n", + " if adults[t] > 30:\n", + " market = adults[t] - 30\n", + " else:\n", + " market = 0\n", + " \n", + " juveniles[t+1] = juveniles[t] + births - maturations\n", + " adults[t+1] = adults[t] + maturations - deaths - market\n", + " \n", + " system.adults = adults\n", + " system.juveniles = juveniles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Test your changes in `run_simulation`:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
value
010.000000
15.000000
25.470000
36.209900
47.057723
58.021560
69.117031
710.362107
811.777219
913.385586
1015.213601
1117.291261
1219.652658
1322.336542
1425.386953
1528.853947
1632.794414
1734.478600
1836.487431
1937.893339
2039.401924
2140.546917
\n", + "
" + ], + "text/plain": [ + "0 10.000000\n", + "1 5.000000\n", + "2 5.470000\n", + "3 6.209900\n", + "4 7.057723\n", + "5 8.021560\n", + "6 9.117031\n", + "7 10.362107\n", + "8 11.777219\n", + "9 13.385586\n", + "10 15.213601\n", + "11 17.291261\n", + "12 19.652658\n", + "13 22.336542\n", + "14 25.386953\n", + "15 28.853947\n", + "16 32.794414\n", + "17 34.478600\n", + "18 36.487431\n", + "19 37.893339\n", + "20 39.401924\n", + "21 40.546917\n", + "dtype: float64" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_simulation(system)\n", + "system.adults" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plotting the results\n", + "\n", + "Here's a version of `plot_results` that plots both the adult and juvenile `TimeSeries`." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def plot_results(system, title=None):\n", + " \"\"\"Plot the estimates and the model.\n", + " \n", + " system: System object with `results`\n", + " \"\"\"\n", + " newfig()\n", + " plot(system.adults, 'bo-', label='adults')\n", + " plot(system.juveniles, 'gs-', label='juveniles')\n", + " decorate(xlabel='Season', \n", + " ylabel='Rabbit population',\n", + " title=title)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If your changes in the previous section were successful, you should be able to run this new version of `plot_results`." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('