diff --git a/.gitignore b/.gitignore index 0368c877ea3cd1efcc7bceb1eb6716e5f69dc836..baf55d1273a0851687cda1782c49783933f697fd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ .DS_Store *.zip -*.ipynb # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/Additional Material/Additional Programming Concepts in Python.ipynb b/Additional Material/Additional Programming Concepts in Python.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..48c57d3424d65bbc04a7665ef3c9b58aca2689b8 --- /dev/null +++ b/Additional Material/Additional Programming Concepts in Python.ipynb @@ -0,0 +1,1353 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Additional Programming Concepts in Python\n", + "\n", + "In this notebook, you will learn about additional programming concepts in Python. They are not part of the learning objectives of the course, but you may run into them at some point, or wonder what they are, or find them fun and useful if you already have some programming experience. \n", + "\n", + "*(Much of this material we wrote up in an earlier version of the notebooks, but then moved here when we tweaked the course to fit in the time we have available.)*\n", + "\n", + "## Tuples\n", + "\n", + "### What is a tuple?\n", + "\n", + "The first more complicated data structure we will discuss is a `tuple`. A tuple is a collection of values inside curved brackets. Here is the basic syntax for making a tuple: \n", + "\n", + "```\n", + "my_tuple = (a, b, c, ...etc...)\n", + "```\n", + "\n", + "As a concrete example, this will create a tuple of three integers:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tup1 = (5,6,7)\n", + "print(tup1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Like the other data types we've see, we can see the tuples we create using `%whos`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%whos" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Tuples are like lists, but behave a bit differently than python lists. In fact, we've already seen tuples before in the previous notebook when we were looking at `for` loops!\n", + "\n", + "If you are given a tuple, you can check how long it by using the `len()` function built into python:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "len(tup1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that tuples do not have to contain integers, they can contain any type of data:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# A tuple of strings\n", + "str_tup = ('foo', 'bar')\n", + "print(str_tup)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Different than how numpy arrays are typically used, tuples can even be mixed, with each element of a different type:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mixed_tup = (1, 1.05, 7+3j, 'foo')\n", + "print(mixed_tup)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And you can even have tuples of tuples:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tup_of_tup = (str_tup, mixed_tup)\n", + "print(tup_of_tup)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Tuples support all the same indexing and slicing as arrays. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Tuples can also contain other tuples! If your tuple contains another tuple, like the example `tup_of_tup`, you can use the square brackets a second time to index into the tuple you extract by the first set of square brackets:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(tup_of_tup)\n", + "print(tup_of_tup[0])\n", + "print(tup_of_tup[0][0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Looping over tuples without using indices\n", + "\n", + "As mentioned briefly in Notebook 2, python `for` loops can also directly iterate over \"iteratable\" objects. \n", + "\n", + "The `tuple` (along with lists, which we will see in a bit, and numpy arrays, which we will see in the next notebook), is one such iteratable objecte. \n", + "\n", + "For example, to print all of entries of a `tuple` out in order, we can use directly following:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for n in tup1:\n", + " print(n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "During each subsequent iteartion of the loop, the variable `n` will be assigned to the next item that is stored in the tuple. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Lists\n", + "\n", + "In this section, we will introduce a very commonly used data structure in python: the `list`. \n", + "\n", + "A list is a list of values, like a `tuple`, but that is made using square brackets:\n", + "\n", + "```\n", + "my_list = [a, b, c, ...etc...]\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "l1 = list(range(10))\n", + "l1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Like tuples, you can extract single elements of the list using indexing, and extract portions of the list using slicing:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(l1[0])\n", + "print(l1[0:5])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "OK, so far so good. But if I have `tuple`, why would I ever want a list?\n", + "\n", + "### Lists vs tupples: Tupples are \"immutable\", lists are \"mutable\"\n", + "\n", + "This is a bit of python-speak for saying that you cannot change the values of a tupple, but you can change the values of a list.\n", + "\n", + "What does this mean? It means if I have a list `[5,6,7]` and I want to change the last number in my list to an 8, I can just directly do this:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "l1 = [5,6,7]\n", + "l1[2] = 8" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If I try this with a tuple, I will find that I can't do it!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "t1 = (5,6,7)\n", + "t1[2] = 8" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because of this functionality, **lists are much more powerful as we can change them once we've made them!**\n", + "\n", + "In addition to changing lists by individual indexing, we can also change whole parts of the list using slicing:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "l2 = list(range(10))\n", + "print(l2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Replace three entries by zeros\n", + "l2 = list(range(10))\n", + "l2[4:7] = [0,0,0]\n", + "print(l2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Remove entries from a list by replacing them with an empty list []\n", + "l2 = list(range(10))\n", + "l2[4:7] = [] \n", + "print(l2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Functions for manipulating lists\n", + "\n", + "In fact, our list object itself has functions built in that allow you to change it! Some examples of things we can do:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# This will add an element to the end of the list\n", + "l1.append(10)\n", + "l1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# This will remove an element from the end of the list\n", + "l1.pop()\n", + "l1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are many more functions built into lists, some of which you can find here:\n", + "\n", + "https://docs.python.org/3/tutorial/datastructures.html" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The problem with lists for scientific computing\n", + "\n", + "Lists look an awful lot like numpy arrays: why don't we just use lists? \n", + "\n", + "In scientific computing, it is very common to want to perform numerical operations on <a href=https://en.wikipedia.org/wiki/Row_and_column_vectors>vectors and matrices</a> of numbers. And also, many times in experiments, the data you will take will be represented by a vector of numbers: for example, the position of a particle as a function of time.\n", + "\n", + "A vector is a collection of numbers in a one-dimentional array:\n", + "\n", + "$$\n", + "x = [1, 2, 3, 4, 5]\n", + "$$\n", + "\n", + "In Notebook 3, we already introduced python `list`s. A list is also a vector, right? It certainly looks the same! Why do we need something new? \n", + "\n", + "The reason we need something new is that python `list`s are not designed to work in the same way as we expect vectors to from our mathematics classes. For example, in math:\n", + "\n", + "$$\n", + "2x = [2,4,6,8,10]\n", + "$$\n", + "\n", + "Let's check if this works with lists" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "l = [1, 2, 3, 4, 5]\n", + "print(2*l)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This certainly does something, but it does not do what we want! It has made the list twice as long by appending two of them together!\n", + "\n", + "Also addition and subtraction doesn't work like we would expect:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(l+l)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Addition makes the list twice as long? And:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(l-l)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And subtraction doesn't work at all...clearly, although they look a lot like vectors, in terms of mathematics, lists do not act much like vectors. This is one of the reasons numpy arrays were created." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dictionaries\n", + "\n", + "Another useful data type in python is a \"dictionary\":\n", + "\n", + "https://docs.python.org/3/tutorial/datastructures.html#dictionaries\n", + "\n", + "At a basic level, a dictionary is a bit like a list that supports non-numeric indices. Dictionaries can be created using the curly brackets in python `{` and `}`. \n", + "\n", + "Here we will create an empty dictionary and start filling it:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "delft_lunch_rating = {}\n", + "delft_lunch_rating['greek olive'] = 10\n", + "delft_lunch_rating['brandmeester'] = 7\n", + "delft_lunch_rating['aula'] = \"expensive\"\n", + "delft_lunch_rating['citg'] = \"bad\"\n", + "delft_lunch_rating['doner'] = \"good but a bit salty\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is what our dictionary looks like:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(delft_lunch_rating)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "I can then look up values in my dictionary using the \"keys\":" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "delft_lunch_rating['aula']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are also functions for getting all the keys:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "delft_lunch_rating.keys()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "My dictionaries can also hold lists if I want:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "delft_lunch_rating[\"greek olive\"] = ['good', 10]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dictionaries are actually a way to implement a basic database in python (I use them in my grading scripts to look up the email addresses and student numbers of a given netid...)\n", + "\n", + "And the Jupyter notebook files actually store a list cells, and each cell consist of a dictionary that contains the text of the cell (and other fun things like metadata). You can see this in action using the <a href=https://nbformat.readthedocs.io/en/latest/api.html>nbformat</a> library, you can actually load a notebook file into your python kernel and poke around it to see what it looks like. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Strings\n", + "\n", + "In notebook 1, we already saw strings as variable types. \n", + "\n", + "It turns out that strings are not just simple (immutable) variables like `int`s and `float`s: `str`s are actually data structures that are indexable (like `tuple`s and `list`s). \n", + "\n", + "Strings are immutable, which means they cannot be changed. But they do have lots of built-in functions that can return a new string (or lots of other things!). \n", + "\n", + "Let's look at an example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "s1 = \"This is a string\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Indexing a string returns the characters of the string:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(s1[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also slice:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(s1[0:6])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Strings do not allow you to directly change " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Built-in string functions\n", + "\n", + "Strings have a bunch of useful built-in functions: \n", + "\n", + "https://docs.python.org/3/library/stdtypes.html#string-methods\n", + "\n", + "some of which we will look at here:\n", + "\n", + "### Splitting a string\n", + "\n", + "Strings have a built-in function `split()`. By default, it will return a list of \"words\" by using whitespaces as the separator:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "s1.split()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Passing `.` as an argument, `split()` will use that as a separator, which is useful for working with filenames:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "input_file = 'myfile.dat'\n", + "output_file = input_file.split('.')[0]\n", + "output_file += \"_processed.dat\"\n", + "print(output_file)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Replacing parts of strings\n", + "\n", + "The function `replace()` replace substrings in your string for you:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "s2 = \"This is a long sentence. It is a good idea to end it.\"\n", + "print(s2)\n", + "print(s2.replace(\" is\", \" was\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that without the space, it will also replace the \"is\" in \"This\":" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(s2)\n", + "print(s2.replace(\"is\", \"was\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Testing for the contents of a string\n", + "\n", + "You can check if a substring is found inside a string using the `in` logical operator:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if \"this\" in \"somewhere in this sentence\":\n", + " print(\"We found a 'this'\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# It is case sensitive:\n", + "if \"this\" in \"This is a sentence\":\n", + " print(\"This will not get printed\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# But you can use the .lower() function of a string to do case insensitive checks\n", + "s3 = \"This is a sentence\"\n", + "if \"this\" in s3.lower():\n", + " print(\"Using .lower(), s3 is converted to all lower-case:\\n\")\n", + " print(\"s3.lower = '%s'\\n\" % s3.lower())\n", + " print(\"And now we do find the substring 'this'\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, we can also see an example of special characters in strings: a `\\n` in a string specifies a \"new line\":\n", + "\n", + "https://docs.python.org/3/reference/lexical_analysis.html#strings\n", + "\n", + "Note that if you want to print a backslash `\\`, you need to put a double backslash in your string: `\\\\`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### String formatting\n", + "\n", + "Until now, we have been printing values of our variables using the standard `str()` conversion of numbers to strings:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "a = 11/300\n", + "print(\"The value of a is\", a)\n", + "\n", + "# The above print() statement is equivalent to:\n", + "\n", + "output = \"The value of a is\"\n", + "output += \" \"\n", + "output += str(a)\n", + "print(output)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But maybe we don't want so many digits in our text output. Say I want only two digits. How can I do this? \n", + "\n", + "For this, python supports \"string formatting\". My personal preference is to work with traditional \"printf-style\" formatting, inherited from the C programming language:\n", + "\n", + "https://docs.python.org/3/library/stdtypes.html#printf-style-bytes-formatting\n", + "\n", + "It sounds a bit scary at first, but it's actually pretty easy to use. It works by using a special operator `%` that works with strings. \n", + "\n", + "To use it, you include a special text in your string that starts with `%`, followed by a sequence of numbers and letters that you use to tell python how you want the string to be formatted. \n", + "\n", + "Here are some examples:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Floating point format with 4 digits\n", + "print(\"The value of a is %.4f\" % a)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Exponential notation with two digits\n", + "print(\"The value of a is %.2e\" % a)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# A string with the formatting not at the end\n", + "print(\"The value of a is %.2e seconds\" % a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Some additional matrix creation routines\n", + "\n", + "There are several functions for making matrices which you may find useful someday: \n", + "\n", + "https://docs.scipy.org/doc/numpy/reference/routines.array-creation.html\n", + "\n", + "including this one which I use often:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# The identity matrix\n", + "print(np.eye(10))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# A band diagonal matrix\n", + "print(np.eye(10,k=-1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Mutable objects and \"call by reference\"\n", + "\n", + "### The `=` operator\n", + "\n", + "Now that we have introduced some more advanced data types, it is time to go back and revisit one of our first topics: the `=` opeartor.\n", + "\n", + "At the start of the first notebook, we introduced the **assignment operator** `=`, and saw that it could be used to give new values to a variable based on the value of another variable:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "a = 5\n", + "b = a\n", + "print(b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What happens if we change the value of `a` after the statment `b = a`? For example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "a = 5\n", + "b = a\n", + "a = 6" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What value does `b` have? Does it have the value of `5` that `a` had when we performed the assignment operation, or does it have `6` (the new values of `a`)? \n", + "\n", + "The obvious answer would be that `b` should have the answer `5`, right? Let's check it:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "a = 5\n", + "b = a\n", + "a = 6\n", + "print(b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "OK, that seems to make sense. \n", + "\n", + "Now let's take a look at and examples with lists. We will create a list `a`, using the assignment operator to make a list variable `b = a`, and then change the values in the list `a`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "a = [2,1]\n", + "b = a\n", + "a[0] = 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now question: did `a[0] = 1` change the value of of `b`? Let's check:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Changing the values of `a` also changed list `b` for some reason? **WHY!!!!?????!!! WHAT IS GOING ON?!!?**\n", + "\n", + "### \"Call by value\" and \"Call by reference\"\n", + "\n", + "To understand what is going on here, we have to understand how computer programs store variables and their values. \n", + "\n", + "When I create a variable `a` in a python by giving it a value `5`, for example, python creates a space in your computers memory where it puts the value `5` and then makes a name for that variable `a` in the kernel's list of variables that points to that spot in memory. \n", + "\n", + "For some types of objects in python, specifically \"immutable\" (\"unchangeable\") object types, when you execute the statement `b = a`, python will create a new spot in your computers memory for variable `b`, copy the value `5` into that memory spot, and then makes variable `b` point to this new spot in the kernel's list of variables. \n", + "\n", + "This procedure is called \"call by value\" in programming languages, and is illustrated here: \n", + "\n", + "<img src=\"resource/asnlib/public/call_by_value.png\"></img>\n", + "\n", + "For \"mutable\" objects, python uses a different concept: \"call by reference\". In call by reference, `b = a` instead does the following: it make a new variable `b` in the list of variables, and make it point to the spot in memory where variable `a` is stored, illustrated here:\n", + "\n", + "<img src=\"resource/asnlib/public/call_by_reference.png\"></img>\n", + "\n", + "It is now obvious why changing the values in `a` will also changes the values in `b`: it is because they point to the same data in your computers memory.\n", + "\n", + "\"Call by reference\" also holds for when you are passing variables to functions:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def set_first_entry_to_zero(x):\n", + " x[0] = 0\n", + "\n", + "a = [1,2]\n", + "set_first_entry_to_zero(a)\n", + "print(a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that our function changed the value of the variable mylist! This was not possible with integers for example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def set_to_zero(x):\n", + " x = 0\n", + "\n", + "a = 1\n", + "set_to_zero(a)\n", + "print(a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Why use \"call by reference\" at all? I find it confusing!\n", + "\n", + "You might ask: why python does this? Well, one reason is that it is that lists, and in particular numpy arrays that we will look at next, can sometime become very big, taking up 100 MB of memory or more. If python used \"call by value\" for such big things all the time, it would use up massive amounts of memory! Every function call or assignment statement would accdientally use up another 100 MB of memory! By using \"call by reference\", it can avoid accidentally filling up your computers memory every time you use the `=` operator. \n", + "\n", + "If you really want to have a *copy* of a list (or a numpy array), these objects typically have `copy()` functions built in that return instead a copy of the object, for when you really need one. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "a = [2,1]\n", + "b = a.copy()\n", + "print(b)\n", + "a[0] = 1\n", + "print(b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, `b` is unaffected by your changes to `a` because the name `b` points to a new copy of the array in memory." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Interactive plots with ipywidgets\n", + "\n", + "One of the cool things that is easy to do in Jupter notebooks is to make \"interactive\" plots. \n", + "\n", + "For example, in the projectile example above, I may want to be able to play with the angle and see how this changes my trajectory. For this, there is a very easy to use and convenient library called `ipywidgets`.\n", + "\n", + "The way it works is we make a function that generates our plot that takes the parameter we want to play with as an argument. We then call an `ipywidgets` function called `interact()`, and it will automatically make a \"live update\" plot in your web browser in which you can adjust the parameter an see how the plot changes. \n", + "\n", + "Let's look at an example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from ipywidgets import interact\n", + "\n", + "v0 = 10 # m/s\n", + "g = 9.8 # m/s^2\n", + "\n", + "# We will allow theta to be adjusted and start it at 45 degrees\n", + "def update_plot(theta=45):\n", + " theta *= np.pi/180 # convert to radians\n", + " y = -g/(2*v0**2*np.cos(theta)**2)*x**2 + x*np.tan(theta)\n", + " plt.plot(x,y)\n", + " plt.ylim(-1,6) # Manually set the ylimits\n", + " plt.xlabel(\"Distance (m)\")\n", + " plt.ylabel(\"Height (m)\")\n", + " plt.axhline(0, ls=\":\", c=\"grey\")\n", + " plt.show()\n", + " \n", + "# Now we call interact, and give it a tuple specifiying the min, max and step for the theta slider\n", + "interact(update_plot, theta=(0,89,2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is a bit slow in updating if you wiggle it too much with the mouse, but if you click on the slider and adjust it using the arrow keys on your keyboard, it works pretty well. \n", + "\n", + "If you are fitting a line to your data, this can also be very useful for getting a good initial guess:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def update_plot2(slope=0.5):\n", + " line = t*slope\n", + " plt.plot(t,v, '.')\n", + " plt.plot(t,line, lw=4)\n", + " plt.xlabel(\"Time (s)\")\n", + " plt.ylabel(\"Voltage (V)\")\n", + " plt.show()\n", + "\n", + "interact(update_plot2, slope=(0,10,0.1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is also easy to make two sliders:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def update_plot3(slope=0.5, offset=0):\n", + " line = t*slope+offset\n", + " plt.plot(t,v, '.')\n", + " plt.plot(t,line, lw=4)\n", + " plt.xlabel(\"Time (s)\")\n", + " plt.ylabel(\"Voltage (V)\")\n", + " plt.show()\n", + "\n", + "interact(update_plot3, slope=(0,10,0.1), offset=(-4,3,0.2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Functions\n", + "\n", + "### Keyword and optional arguments\n", + "\n", + "In addition to the \"positional\" arguments we introduced earlier, python also supports another type of argument: the \"keyword\" argument. These are often used for \"optional\" arguments that you don't neccessarily need but may want to give the user the option of using. The syntax is:\n", + "\n", + "```\n", + "def function_name(var1, optional_var2 = default_value)\n", + " ...\n", + "```\n", + "\n", + "The \"default_value\" is the value that the optional argument will have if it is not specified by the user. \n", + "\n", + "In python-speak, these \"optional\" arguement as called \"keyword\" arguments, and the normal arguments we introduced above are called \"positional\" arguments. In python, in both defining and using functions, keyword arguments must always come after all of the positional arguments. \n", + "\n", + "Here, we will show an example where we use an optional parameter to change the way we print the status sentence. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def print_status4(x, long=True):\n", + " if long:\n", + " print(\"The value of the first input variable is \", x)\n", + " else:\n", + " print(\"Val is \", x)\n", + "\n", + "print_status4(1)\n", + "print_status4(2.5, long=True)\n", + "print_status4(2.4, long=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because python assigns the value of keyword argument variables in the function by matching the keyword and not their position in the list, if you have multiple keyword arguments, you can also change the order of them when you use the function. \n", + "\n", + "For example, if I define a function:\n", + "\n", + "```\n", + "def myfunction(x, var1=1, var2=4):\n", + " ...\n", + "```\n", + "\n", + "then both of these would do the same thing:\n", + "\n", + "```\n", + "myfunction(1,var1=3, var2=54)\n", + "myfunction(1,var2=54, var2=3)\n", + "```\n", + "\n", + "Finally, one can also use keywords as a way to send values to functions even if the functions are not defined with keyword arguments. This allows you to change to order of variables you send to a function if you want. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def myfun(x,y):\n", + " print(\"x is\", x)\n", + " print(\"y is\", y) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "myfun(1,2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "myfun(2,1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "myfun(x=1,y=2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "myfun(y=2,x=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Python Error Messages with functions\n", + "\n", + "In the first notebook, we learned some of the basics of how to understand python errors.\n", + "\n", + "Sometimes, though, if you are using functions from a library, the error messages can get very long, and trickier to understand. \n", + "\n", + "Here, we will look at how to dissect an example of a more complicated error you can get from a function in a library and how to figure out where the useful information is." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "plt.plot([1,2], [1,2,3])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Wow, that was a really big error message. What do I do with all of this? \n", + "\n", + "The most important information is at the very top and at the very bottom (you can just skip the rest for now...). \n", + "\n", + "At the top, it shows us the part of our code that triggered the error:\n", + "\n", + "<img src=\"resource/asnlib/public/big_error_1.png\"></img>\n", + "\n", + "The error type is a `ValueError`, which according to the documentation, indicates \"an argument that has the right type but an inappropriate value\". \n", + "\n", + "In the middle there is then a whole bunch of stuff we won't easily understand. What is all of this? This is showing us what is happening inside all the functions of the matplotlib library...probably unless you are a bit of an expert, you will not really understand all of this. \n", + "\n", + "We can learn more, though, by looking at the last line:\n", + "\n", + "<img src=\"big_error_2.png\"></img>\n", + "\n", + "What are `x` and `y`? They are the variable names in the library function in matplotlib where we ended up, so probably also maybe not immediately obvious what they are. But we can see more about the problem: it is complaining that two of the variables do not have the same shape. \n", + "\n", + "If we look up at the line in our code that triggered the error, we can see that we have supplied two arguments that have a different number of elements: `plt.plot([1,2], [1,2,3])`\n", + "\n", + "It would seem that both the first and second variables of the `plot` function should have the same number of elements. Indeed, if we try:\n", + "\n", + "`plt.plot([1,2,3], [1,2,3,])`\n", + "\n", + "then the error goes away:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.plot([1,2,3], [1,2,3,])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Learning objectives list\n", + "\n", + "Not crucial since this notebook is optional, but maybe useful to have.\n", + "\n", + "**Learning objectives for this notebook:**\n", + "\n", + "* Student is able to create and index tuples and lists by hand and using the `range()` operator\n", + "* Student is able to loop over tuples and lists without indexing\n", + "* Student is able to extract subsets of lists and tuples using slicing\n", + "* Student is able to change individual entries of a list using indexing and the assignment operator\n", + "* Student is able to use built-in functions of lists \n", + "* Student is able to use indexing to extract substrings from a string\n", + "* Student is able to use built-in string functions\n", + "* Student is able to split a string into a list of strings using the `.split()` function\n", + "* Student is able to search in strings using the `in` operator\n", + "* Student is able to use formating and the `%` opereator to control how variables are translated into strings\n", + "* Student is able to predict how variables behave differently for \"mutable\" and \"non-mutable\" objects (call-by-value vs. call-by-reference)\n", + "* Student is able to use the `ipywidgets` `interact()` function to explore functions using sliders\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "jupytext": { + "formats": "ipynb,md" + }, + "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.7.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": true, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": true + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Additional Material/Additional Programming Concepts in Python.md b/Additional Material/Additional Programming Concepts in Python.md index 7e11573e8d814fa4dc4316eb2cc7b8b98d21eb63..09dd3d1a360a7c441430c738777ebe333468720b 100644 --- a/Additional Material/Additional Programming Concepts in Python.md +++ b/Additional Material/Additional Programming Concepts in Python.md @@ -13,7 +13,6 @@ jupyter: name: python3 --- -<!-- #region --> # Additional Programming Concepts in Python In this notebook, you will learn about additional programming concepts in Python. They are not part of the learning objectives of the course, but you may run into them at some point, or wonder what they are, or find them fun and useful if you already have some programming experience. @@ -31,7 +30,6 @@ my_tuple = (a, b, c, ...etc...) ``` As a concrete example, this will create a tuple of three integers: -<!-- #endregion --> ```python tup1 = (5,6,7) @@ -101,7 +99,7 @@ for n in tup1: During each subsequent iteartion of the loop, the variable `n` will be assigned to the next item that is stored in the tuple. -<!-- #region --> + ## Lists In this section, we will introduce a very commonly used data structure in python: the `list`. @@ -111,7 +109,6 @@ A list is a list of values, like a `tuple`, but that is made using square bracke ``` my_list = [a, b, c, ...etc...] ``` -<!-- #endregion --> ```python l1 = list(range(10)) @@ -505,11 +502,11 @@ For some types of objects in python, specifically "immutable" ("unchangeable") o This procedure is called "call by value" in programming languages, and is illustrated here: -<img src="call_by_value.png"></img> +<img src="resource/asnlib/public/call_by_value.png"></img> For "mutable" objects, python uses a different concept: "call by reference". In call by reference, `b = a` instead does the following: it make a new variable `b` in the list of variables, and make it point to the spot in memory where variable `a` is stored, illustrated here: -<img src="call_by_reference.png"></img> +<img src="resource/asnlib/public/call_by_reference.png"></img> It is now obvious why changing the values in `a` will also changes the values in `b`: it is because they point to the same data in your computers memory. @@ -613,7 +610,6 @@ def update_plot3(slope=0.5, offset=0): interact(update_plot3, slope=(0,10,0.1), offset=(-4,3,0.2)) ``` -<!-- #region --> ## Functions ### Keyword and optional arguments @@ -630,7 +626,6 @@ The "default_value" is the value that the optional argument will have if it is n In python-speak, these "optional" arguement as called "keyword" arguments, and the normal arguments we introduced above are called "positional" arguments. In python, in both defining and using functions, keyword arguments must always come after all of the positional arguments. Here, we will show an example where we use an optional parameter to change the way we print the status sentence. -<!-- #endregion --> ```python def print_status4(x, long=True): @@ -644,7 +639,6 @@ print_status4(2.5, long=True) print_status4(2.4, long=False) ``` -<!-- #region --> Because python assigns the value of keyword argument variables in the function by matching the keyword and not their position in the list, if you have multiple keyword arguments, you can also change the order of them when you use the function. For example, if I define a function: @@ -662,7 +656,6 @@ myfunction(1,var2=54, var2=3) ``` Finally, one can also use keywords as a way to send values to functions even if the functions are not defined with keyword arguments. This allows you to change to order of variables you send to a function if you want. For example: -<!-- #endregion --> ```python def myfun(x,y): @@ -705,7 +698,7 @@ The most important information is at the very top and at the very bottom (you ca At the top, it shows us the part of our code that triggered the error: -<img src="big_error_1.png"></img> +<img src="resource/asnlib/public/big_error_1.png"></img> The error type is a `ValueError`, which according to the documentation, indicates "an argument that has the right type but an inappropriate value". diff --git a/Additional Material/big_error_1.png b/Additional Material/resource/asnlib/public/big_error_1.png similarity index 100% rename from Additional Material/big_error_1.png rename to Additional Material/resource/asnlib/public/big_error_1.png diff --git a/Additional Material/big_error_2.png b/Additional Material/resource/asnlib/public/big_error_2.png similarity index 100% rename from Additional Material/big_error_2.png rename to Additional Material/resource/asnlib/public/big_error_2.png diff --git a/Additional Material/call_by_reference.png b/Additional Material/resource/asnlib/public/call_by_reference.png similarity index 100% rename from Additional Material/call_by_reference.png rename to Additional Material/resource/asnlib/public/call_by_reference.png diff --git a/Additional Material/call_by_value.png b/Additional Material/resource/asnlib/public/call_by_value.png similarity index 100% rename from Additional Material/call_by_value.png rename to Additional Material/resource/asnlib/public/call_by_value.png diff --git a/Course_Information/Course_Information_anaconda_prompt_windows.png b/Course_Information/Course_Information_anaconda_prompt_windows.png new file mode 100644 index 0000000000000000000000000000000000000000..4b716c0421a909588486e6aa3b49b281f718dbaf Binary files /dev/null and b/Course_Information/Course_Information_anaconda_prompt_windows.png differ diff --git a/Course_Information/Course_Information_cell_type.png b/Course_Information/Course_Information_cell_type.png new file mode 100644 index 0000000000000000000000000000000000000000..adf5ee25fa7100c663b9f103a74d0ad7f23316e3 Binary files /dev/null and b/Course_Information/Course_Information_cell_type.png differ diff --git a/Course_Information/Course_Information_disable_box.png b/Course_Information/Course_Information_disable_box.png new file mode 100644 index 0000000000000000000000000000000000000000..192cd4b02ef714ecb065ceb0e7f00a5a6f4b37cb Binary files /dev/null and b/Course_Information/Course_Information_disable_box.png differ diff --git a/Course_Information/Course_Information_enable_toc.png b/Course_Information/Course_Information_enable_toc.png new file mode 100644 index 0000000000000000000000000000000000000000..ea580c57effd2de727f0c8366ee23989a9dc0c72 Binary files /dev/null and b/Course_Information/Course_Information_enable_toc.png differ diff --git a/Course_Information/Course_Information_mac_terminal.png b/Course_Information/Course_Information_mac_terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..fe67180a546001631a0da7da061ba96054c90f52 Binary files /dev/null and b/Course_Information/Course_Information_mac_terminal.png differ diff --git a/Course_Information/Course_Information_nbextensions.png b/Course_Information/Course_Information_nbextensions.png new file mode 100644 index 0000000000000000000000000000000000000000..baa08582d0d6b6385531eb2c02a24442843410e1 Binary files /dev/null and b/Course_Information/Course_Information_nbextensions.png differ diff --git a/Course_Information/Course_Information_notebook_server.png b/Course_Information/Course_Information_notebook_server.png new file mode 100644 index 0000000000000000000000000000000000000000..a21a0b1a3dc720de418e12272f76d165f02302a1 Binary files /dev/null and b/Course_Information/Course_Information_notebook_server.png differ diff --git a/Course_Information/Frequently Asked Questions.ipynb b/Course_Information/Frequently Asked Questions.ipynb index ea334dccae2999aa882817e395b1809c863f5041..e9b261e832a617d83ff3b9f5d4df5bfd6a106578 100644 --- a/Course_Information/Frequently Asked Questions.ipynb +++ b/Course_Information/Frequently Asked Questions.ipynb @@ -71,7 +71,7 @@ "\n", "You have probably changed the \"cell type\" from \"Code\" to \"Markdown\". You can change it back using the menu toolbar dropdown box: \n", "\n", - "<img src=\"cell_type.png\"></img>\n", + "<img src=\"resource/asnlib/public/Course_Information_cell_type.png\"></img>\n", "\n", "There also <a href=https://jupyter-notebook.readthedocs.io/en/stable/examples/Notebook/Notebook%20Basics.html#Keyboard-Navigation>keyboard shortcuts</a> for many things. \n", "\n", diff --git a/Course_Information/Installation Instructions.ipynb b/Course_Information/Installation Instructions.ipynb index fa755bd73644f976f1224348f47f06b8e29d7e2c..7b589545db7d7c6af41dfb229a0bdcdf6974e504 100644 --- a/Course_Information/Installation Instructions.ipynb +++ b/Course_Information/Installation Instructions.ipynb @@ -27,11 +27,11 @@ "\n", "On **Windows**, open an \"Anaconda prompt\" (type \"Anaconda prompt\" in search in Windows 10, or look for it in the program menu):\n", "\n", - "<img src=\"anaconda_prompt_windows.png\" width=50%></img>\n", + "<img src=\"resource/asnlib/public/Course_Information_anaconda_prompt_windows.png\" width=40%></img>\n", " \n", "On **MacOS**, open a \"Teminal\" app (use \"Spotlight Search\" and type \"Terminal\"): \n", "\n", - "<img src=\"mac_terminal.png\" width=40%></img>\n", + "<img src=\"resource/asnlib/public/Course_Information_mac_terminal.png\" width=40%></img>\n", "\n", "At the command line of the terminal, run the following command by copy-pasting in the text and typing \"enter\":\n", "\n", @@ -47,7 +47,7 @@ "\n", "This will start the notebook server and automatically open a tab in your web browser sending you to the (local) web address of the notebook server running on your computer, showing you the files in your home directory:\n", "\n", - "<img width=70% src=\"notebook_server.png\"><img>\n", + "<img width=70% src=\"resource/asnlib/public/Course_Information_notebook_server.png\"><img>\n", "\n", "If you close your browser, you can always use this link to get back to your local notebook server:\n", "\n", @@ -65,15 +65,15 @@ "\n", "Click on the \"Nbextensions\" tab:\n", "\n", - "<img src=\"nbextensions.png\" width=50%></img> \n", + "<img src=\"resource/asnlib/public/Course_Information_nbextensions.png\" width=50%></img> \n", "\n", "Make sure that this box is not clicked:\n", "\n", - "<img src=\"disable_box.png\" width=50%></img>\n", + "<img src=\"resource/asnlib/public/Course_Information_disable_box.png\" width=50%></img>\n", "\n", "<p> And enable the \"Table of Contents\" extension:\n", "\n", - "<img src=\"enable_toc.png\" width=50%></img>\n", + "<img src=\"resource/asnlib/public/Course_Information_enable_toc.png\" width=50%></img>\n", "\n", "## Frequently asked questions\n", "\n", diff --git a/Course_Information/Python in the practicum.ipynb b/Course_Information/Python in the practicum.ipynb index e2f0e1fd1c897054d2a2e264d6deb852056ef25d..8df6e83c5a849b75ebd42112c627d48fcf1896fa 100644 --- a/Course_Information/Python in the practicum.ipynb +++ b/Course_Information/Python in the practicum.ipynb @@ -18,22 +18,34 @@ "* **Notebook 4: Scientific Computing with Numpy**\n", "* **Notebook 5: Data in Python**\n", "\n", - "For the material of Notebooks 1-3, we have an assignment connected to each notebook, and for Notebooks 4 and 5, there will be a final project that will cover the material in those notebooks:\n", + "There are also to additional notebooks: one with advice on good coding practices, and one on \"Advanced programming concepts\" for those that are interested:\n", "\n", - "* **Assignment 1 (Must pass)**\n", - "* **Assignment 2 (Must pass)**\n", - "* **Assignment 3 (Must pass)**\n", - "* **Final Project (100%)**\n", + "* **Good coding practices** \n", + "* **Advanced programming concepts** \n", "\n", - "The assignments will be checked in class. You must have completed and passed the three assignments in order to be allowed to submit your final project. \n", + "**Good coding practices** is a short notebook that you should read and apply in your assignments. **Advanced programming concepts** is optional in this course. \n", + "\n", + "For the material of Notebooks 1-3, we have an assignment connected to each notebook, and for Notebooks 4 and 5, there will be a final project applying what you have learned to data analysis. The python component of this course makes up 15% of your final grade. Each of the assignments will count for 3 points out of 15 towards your python grade component, and the final project will count for 6 points of 15. \n", + "\n", + "* **Assignment 1 (3 points)** Due date: Tuesday, 8 Sep at 17.59\n", + "* **Assignment 2 (3 points)** Due date: Tuesday, 15 Sep at 17.59\n", + "* **Assignment 3 (3 points)** Due date: Tuesday, 22 Sep at 17.59\n", + "* **Final Project (6 points)** Due date: Friday, 25 Sep at 17.59\n", + "\n", + "The assignments must be submitted before the assigned deadlines by using the \"Submit\" button in Vocareum.\n", + "\n", + "## Platform\n", + "\n", + "You will be working in an online cloud-based computing platform called \"Vocareum\". You will access Vocareum through the links that will be created in the brightspace course page. \n", + "\n", + "There will be information available in the course MS Teams \"Python\" channel about how to use Vocareum. \n", "\n", "## Pace\n", "\n", "While there are 5 lectures and 5 notebooks, we encourage you to work at your own pace through the material. More experienced programmers may cover the material more quickly, perhaps with more than one notebook worth of material per lecture, while those with less (or no) programming experience may want to go through the material more slowly and finish the material at home. \n", "\n", - "## Final Grade\n", + "You should make sure, however, that you cover the material at a sufficient pace that you do not miss the deadlines for the assignments. \n", "\n", - "Your python grade will count for 18% of the practicum grade (1ECTS). You grade will be determined by the final project, but your project will only be graded if you have passed all three assignments, which must be checked by a TA or lecturer in the lecture sessions (more on this below). \n", "\n", "## What do I do with the \"Lecture\" notebooks? \n", "\n", @@ -47,31 +59,19 @@ "\n", "Try it yourself! Feel free to add code cells to the notebook (using the \"Insert\" menu) to try things out. If you find something you don't understand, you are always welcome to ask a TA or a lecturer to have a look.\n", "\n", - "## Assignment Notebooks\n", + "## Assignments\n", "\n", "For lectures 1-3, there are \"Assignment Notebooks\" which we will ask you to complete.\n", "\n", "The Assignment notebooks are designed to test if you have learned and understood the material from the lecture notebooks and have achieved the learning objective we have defined. Once you think you have mastered the material of the lecture notebooks and have played with writing code in the Try it yourself code cells and the exercise cells, you should move on to the Assignment Notebooks.\n", "\n", - "For the assignment notebooks, you should write your answer in the provided cells. When you think you have the correct answers, bring your laptop to a TA or lecturer and have them check your code. If you code is correct and you can explain it properly, then the TA will approve your completion of that assignment and you be marked as a \"Pass\".\n", - "\n", - "Our pass criteria are:\n", + "Your assignments will be submitted via Vocareum, and **must be submitted before the submission deadline**. In the week of October 5th, there will be short 10 minute examinations with the teaching assistants to determine your grade. In this examination, you will sit with a TA for 5 minutes, quickly demonstrate your code, and explain what the code does. The TA will then take 5 minutes on their own to assess the following criteria:\n", "\n", "* The code achieves what is asked in the question\n", "* The student is able to explain what the code is doing to the TA\n", "* The code cells contains comments explaining what it is doing\n", "\n", - "If the TA determines that your assignment has not satisfied these criteria, then you can continue to work on it and have it checked again when you think it is correct.\n", - "\n", - "Before the TA marks your assignment as a \"Pass\", you will be required to upload that code to the appropriate assignment folder on Brightspace.\n", - "\n", - "The assignments must be checked and approved by a TA before the following deadlines:\n", - "\n", - "* Assignment 1: End of session P3\n", - "* Assignment 2: End of session P4\n", - "* Assignment 3: End of session P5\n", - "\n", - "Individual exceptions are possible for reasons such as illness in discussion with the lecturers.\n", + "A more detailed rubric for the assessment of assignments 1-3 will be made public when ready.\n", "\n", "### Am I allowed to ask for help? \n", "\n", @@ -83,23 +83,10 @@ "\n", "No! While we encourage you to ask fellow students for help, you are not allowed to directly cut-and-paste they code (or copy their notebook files). At the end of the course, we will be performing a specialized plagiarism scan (designed for computer code) on the submitted assignments and any anomalies will be investigated. \n", "\n", - "## Final Project\n", - "\n", - "In addition to the assignments related to the core programming concepts, we will also have a final assignment that will determining your the final grade. \n", - "\n", - "In this final assignment, you will load, process, plot and analyze a dataset using python. You will create a lab-report-style notebook in which you summarize your findings and you will submit this via Brightspace for grading by the TAs. \n", + "## Python Final Project\n", "\n", - "The final projects will be submitted via Brightspace in an assignment folder with a deadline announced per student group. \n", - "\n", - "**Important:** Your final project will NOT be graded unless you have achieved a pass evaluation on all the assignments as described above. " + "In addition to the assignments related to the core programming concepts, we will also have a final assignment that will be connected to the material of Lecture Notebooks 4 and 5 in which you will apply your python skills for loading, plotting, and analysing data. More information on the format and assessment of the Python Final Project will follow soon. " ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { @@ -121,7 +108,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.5" }, "toc": { "base_numbering": 1, diff --git a/Course_Information/Python in the practicum.md b/Course_Information/Python in the practicum.md index 5ae7f7bd772db4ea95125834eb1086df6706c477..e4b479cccde518d56f1826324627eedc510984c4 100644 --- a/Course_Information/Python in the practicum.md +++ b/Course_Information/Python in the practicum.md @@ -5,14 +5,15 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: '1.1' - jupytext_version: 1.2.2 + format_version: '1.2' + jupytext_version: 1.3.0 kernelspec: display_name: Python 3 language: python name: python3 --- +<!-- #region --> # Python in the practicum ## Overview @@ -27,22 +28,34 @@ For doing this, we have created a set of 5 "Lecture Notebooks", with the topics: * **Notebook 4: Scientific Computing with Numpy** * **Notebook 5: Data in Python** -For the material of Notebooks 1-3, we have an assignment connected to each notebook, and for Notebooks 4 and 5, there will be a final project that will cover the material in those notebooks: +There are also to additional notebooks: one with advice on good coding practices, and one on "Advanced programming concepts" for those that are interested: -* **Assignment 1 (Must pass)** -* **Assignment 2 (Must pass)** -* **Assignment 3 (Must pass)** -* **Final Project (100%)** +* **Good coding practices** +* **Advanced programming concepts** -The assignments will be checked in class. You must have completed and passed the three assignments in order to be allowed to submit your final project. +**Good coding practices** is a short notebook that you should read and apply in your assignments. **Advanced programming concepts** is optional in this course. + +For the material of Notebooks 1-3, we have an assignment connected to each notebook, and for Notebooks 4 and 5, there will be a final project applying what you have learned to data analysis. The python component of this course makes up 15% of your final grade. Each of the assignments will count for 3 points out of 15 towards your python grade component, and the final project will count for 6 points of 15. + +* **Assignment 1 (3 points)** Due date: Tuesday, 8 Sep at 17.59 +* **Assignment 2 (3 points)** Due date: Tuesday, 15 Sep at 17.59 +* **Assignment 3 (3 points)** Due date: Tuesday, 22 Sep at 17.59 +* **Final Project (6 points)** Due date: Friday, 25 Sep at 17.59 + +The assignments must be submitted before the assigned deadlines by using the "Submit" button in Vocareum. + +## Platform + +You will be working in an online cloud-based computing platform called "Vocareum". You will access Vocareum through the links that will be created in the brightspace course page. + +There will be information available in the course MS Teams "Python" channel about how to use Vocareum. ## Pace While there are 5 lectures and 5 notebooks, we encourage you to work at your own pace through the material. More experienced programmers may cover the material more quickly, perhaps with more than one notebook worth of material per lecture, while those with less (or no) programming experience may want to go through the material more slowly and finish the material at home. -## Final Grade +You should make sure, however, that you cover the material at a sufficient pace that you do not miss the deadlines for the assignments. -Your python grade will count for 18% of the practicum grade (1ECTS). You grade will be determined by the final project, but your project will only be graded if you have passed all three assignments, which must be checked by a TA or lecturer in the lecture sessions (more on this below). ## What do I do with the "Lecture" notebooks? @@ -56,31 +69,19 @@ The notebooks also contain exercises where you can test what you have learned. S Try it yourself! Feel free to add code cells to the notebook (using the "Insert" menu) to try things out. If you find something you don't understand, you are always welcome to ask a TA or a lecturer to have a look. -## Assignment Notebooks +## Assignments For lectures 1-3, there are "Assignment Notebooks" which we will ask you to complete. The Assignment notebooks are designed to test if you have learned and understood the material from the lecture notebooks and have achieved the learning objective we have defined. Once you think you have mastered the material of the lecture notebooks and have played with writing code in the Try it yourself code cells and the exercise cells, you should move on to the Assignment Notebooks. -For the assignment notebooks, you should write your answer in the provided cells. When you think you have the correct answers, bring your laptop to a TA or lecturer and have them check your code. If you code is correct and you can explain it properly, then the TA will approve your completion of that assignment and you be marked as a "Pass". - -Our pass criteria are: +Your assignments will be submitted via Vocareum, and **must be submitted before the submission deadline**. In the week of October 5th, there will be short 10 minute examinations with the teaching assistants to determine your grade. In this examination, you will sit with a TA for 5 minutes, quickly demonstrate your code, and explain what the code does. The TA will then take 5 minutes on their own to assess the following criteria: * The code achieves what is asked in the question * The student is able to explain what the code is doing to the TA * The code cells contains comments explaining what it is doing -If the TA determines that your assignment has not satisfied these criteria, then you can continue to work on it and have it checked again when you think it is correct. - -Before the TA marks your assignment as a "Pass", you will be required to upload that code to the appropriate assignment folder on Brightspace. - -The assignments must be checked and approved by a TA before the following deadlines: - -* Assignment 1: End of session P3 -* Assignment 2: End of session P4 -* Assignment 3: End of session P5 - -Individual exceptions are possible for reasons such as illness in discussion with the lecturers. +A more detailed rubric for the assessment of assignments 1-3 will be made public when ready. ### Am I allowed to ask for help? @@ -92,16 +93,7 @@ Note that the TAs will be happy to help you "debug" your code and point you in No! While we encourage you to ask fellow students for help, you are not allowed to directly cut-and-paste they code (or copy their notebook files). At the end of the course, we will be performing a specialized plagiarism scan (designed for computer code) on the submitted assignments and any anomalies will be investigated. -## Final Project - -In addition to the assignments related to the core programming concepts, we will also have a final assignment that will determining your the final grade. - -In this final assignment, you will load, process, plot and analyze a dataset using python. You will create a lab-report-style notebook in which you summarize your findings and you will submit this via Brightspace for grading by the TAs. - -The final projects will be submitted via Brightspace in an assignment folder with a deadline announced per student group. - -**Important:** Your final project will NOT be graded unless you have achieved a pass evaluation on all the assignments as described above. - -```python +## Python Final Project -``` +In addition to the assignments related to the core programming concepts, we will also have a final assignment that will be connected to the material of Lecture Notebooks 4 and 5 in which you will apply your python skills for loading, plotting, and analysing data. More information on the format and assessment of the Python Final Project will follow soon. +<!-- #endregion --> diff --git a/Course_Information/The Python Online Classroom in TN1405 in 2020 - 2021.ipynb b/Course_Information/The Python Online Classroom in TN1405 in 2020 - 2021.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..aa6b514410151c888e786346f36744bcd96870eb --- /dev/null +++ b/Course_Information/The Python Online Classroom in TN1405 in 2020 - 2021.ipynb @@ -0,0 +1,107 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# The Python Online Classroom in TN1405-P 2020/2021\n", + "\n", + "Due to the circumstances, the course in the 2020/2021 academic year will be run primarily online. Fortunately there are many great tools that will enable you to learn online, work online, and intearct with us and each other online. Particularly for programming, this can be very effective.\n", + "\n", + "For the python, there are three important online tools which we will use in the following way:\n", + "\n", + "* **Brightspace / Vocareum:** Where you log in and work\n", + "* **Microsoft Teams:** A \"virtual classroom\"\n", + "* **Support Forum:** Where you can post questions both inside and outside of lecture hours\n", + "\n", + "In the sections below, I will describe in a bit more detail how we will use them.\n", + "\n", + "## Brightspace / Vocareum\n", + "\n", + "This is where you we will post information and the material for you to work on. Vocareum is our cloud-based computing platform, and is accessed by links from inside Brightspace. \n", + "\n", + "## Microsoft Teams\n", + "\n", + "We will use Microsoft teams as an online classroom where you can work and interact with the teachers, the TAs, and with each other. \n", + "\n", + "Microsoft teams is a \"chat\" based communications tool that organises discussions inside a \"team\" into \"channels\", similar to the platform discord (https://discord.com/) that some of you may be familiar with from online gaming communities.\n", + "\n", + "For Python, the following channels are relevant:\n", + "\n", + "* **Python:** Where I will post things like instructional videos and other information that is hard to post on brightsapce\n", + "* **Tafel 01-10:** Virtual \"tables\" where you can work with other students using text chat, video chat, posting images / screenshots, and sharing documents\n", + "* **TA gevraagd:** A channel where you can request \"live\" help from a TA during lecture hours by mentioning your table name\n", + "\n", + "I will post a video in the Python Teams channel illustrating this in more detail.\n", + "\n", + "The video support is quite handy, it allows screen sharing, and also allows you to let others control your screen for technical support emergencies.\n", + "\n", + "You are welcome to \"sit together\" virtually on Teams whenever you want, also outside of lecture hours! However, the course team (TAs / teachers) will only provide support in Teams during official lecture hours.\n", + "\n", + "## Support Forum\n", + "\n", + "In addition, for python, we will also work with an external support forum:\n", + "\n", + "https://tn1405-forum.quantumtinkerer.tudelft.nl/\n", + "\n", + "You have all been sent an invitation to join the forum at your TU Delft student email address. Note you will have to create a separate password. (If you forget the password, it is easy to reset using your email address.)\n", + "\n", + "The forum is a place where you can ask questions about the course material. **What is the difference between the forum and asking for help in teams?** There are three things:\n", + "\n", + "* The answers the course team provide will be visible to everyone so that people who have a similar problem can already find the answer\n", + "* You might be able to get help from other students as well who had or are having similar problems\n", + "* **Questions in the forum will also be answered outside of office hours**\n", + "\n", + "Some advice and ground rules: \n", + "\n", + "* Don't be shy: just post. We will do our best to answer all questions politely and respectfully.\n", + "* Try to include as much info as you can in your post: details will help us figure out your problem. Posting error messages is highly valuable. \n", + "* **Don't post solution code to the assignments!** Question about problems with the assignment are welcome, but please formulate your questions in words. If we get stuck trying to figure it out, we will ask you to send the code to the course team in a personal message. \n", + "* When you post or reply, do your best to be polite and respectful: let's make this a comfortable place for everyone! \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ] + } + ], + "metadata": { + "jupytext": { + "formats": "ipynb,md" + }, + "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.7.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": false, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Course_Information/The Python Online Classroom in TN1405 in 2020 - 2021.md b/Course_Information/The Python Online Classroom in TN1405 in 2020 - 2021.md new file mode 100644 index 0000000000000000000000000000000000000000..4a019d28b8b1bb45533e8497dfa02e55dcac4bc9 --- /dev/null +++ b/Course_Information/The Python Online Classroom in TN1405 in 2020 - 2021.md @@ -0,0 +1,76 @@ +--- +jupyter: + jupytext: + formats: ipynb,md + text_representation: + extension: .md + format_name: markdown + format_version: '1.2' + jupytext_version: 1.3.0 + kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# The Python Online Classroom in TN1405-P 2020/2021 + +Due to the circumstances, the course in the 2020/2021 academic year will be run primarily online. Fortunately there are many great tools that will enable you to learn online, work online, and intearct with us and each other online. Particularly for programming, this can be very effective. + +For the python, there are three important online tools which we will use in the following way: + +* **Brightspace / Vocareum:** Where you log in and work +* **Microsoft Teams:** A "virtual classroom" +* **Support Forum:** Where you can post questions both inside and outside of lecture hours + +In the sections below, I will describe in a bit more detail how we will use them. + +## Brightspace / Vocareum + +This is where you we will post information and the material for you to work on. Vocareum is our cloud-based computing platform, and is accessed by links from inside Brightspace. + +## Microsoft Teams + +We will use Microsoft teams as an online classroom where you can work and interact with the teachers, the TAs, and with each other. + +Microsoft teams is a "chat" based communications tool that organises discussions inside a "team" into "channels", similar to the platform discord (https://discord.com/) that some of you may be familiar with from online gaming communities. + +For Python, the following channels are relevant: + +* **Python:** Where I will post things like instructional videos and other information that is hard to post on brightsapce +* **Tafel 01-10:** Virtual "tables" where you can work with other students using text chat, video chat, posting images / screenshots, and sharing documents +* **TA gevraagd:** A channel where you can request "live" help from a TA during lecture hours by mentioning your table name + +I will post a video in the Python Teams channel illustrating this in more detail. + +The video support is quite handy, it allows screen sharing, and also allows you to let others control your screen for technical support emergencies. + +You are welcome to "sit together" virtually on Teams whenever you want, also outside of lecture hours! However, the course team (TAs / teachers) will only provide support in Teams during official lecture hours. + +## Support Forum + +In addition, for python, we will also work with an external support forum: + +https://tn1405-forum.quantumtinkerer.tudelft.nl/ + +You have all been sent an invitation to join the forum at your TU Delft student email address. Note you will have to create a separate password. (If you forget the password, it is easy to reset using your email address.) + +The forum is a place where you can ask questions about the course material. **What is the difference between the forum and asking for help in teams?** There are three things: + +* The answers the course team provide will be visible to everyone so that people who have a similar problem can already find the answer +* You might be able to get help from other students as well who had or are having similar problems +* **Questions in the forum will also be answered outside of office hours** + +Some advice and ground rules: + +* Don't be shy: just post. We will do our best to answer all questions politely and respectfully. +* Try to include as much info as you can in your post: details will help us figure out your problem. Posting error messages is highly valuable. +* **Don't post solution code to the assignments!** Question about problems with the assignment are welcome, but please formulate your questions in words. If we get stuck trying to figure it out, we will ask you to send the code to the course team in a personal message. +* When you post or reply, do your best to be polite and respectful: let's make this a comfortable place for everyone! + + + + + + + diff --git a/Good Programming Practices/Good coding practices.ipynb b/Good Programming Practices/Good coding practices.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..03b7ef868065dbd278a511bcda76028921bba347 --- /dev/null +++ b/Good Programming Practices/Good coding practices.ipynb @@ -0,0 +1,550 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Good coding practices\n", + "\n", + "In this notebook, we outline some example of \"good coding practices\": things you should do to make your code robust and understandable to others.\n", + "\n", + "Learning objectives:\n", + "\n", + "* Student is able to write code that has a clear and understandable structure\n", + "* Student is able to write code with descriptive variable names\n", + "* Student is able to write code with explanatory comments\n", + "* Student is able to write code that avoids \"hard coding\"\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f0a26f3edf642ded", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "## Writing understandable code\n", + "\n", + "Once you become an expert at coding, it will become very easy to write code that python can understand.\n", + "\n", + "What is actually a big challenge in computer science is actually writing code that works, that is efficient, and most importantly, that **other people will understand!** \n", + "\n", + "You might think: but I'm writing this code only for myself, so I don't really care about if my code is easy to understand, right? \n", + "\n", + "There are many reasons why this is incorrect: \n", + "\n", + "1. In this course, your teachers and your TAs will need to understand your code in order to grade it. If we can't quickly understand what you have done, **this will affect your grade!!!** (One of our grading criteria will be the clarity and readability of your code.)\n", + "\n", + "2. You might, in the future, want to share your code with someone else. If they have to spend a lot of time figuring out what you've done, then your code is useless to them. (In fact, this is the power of open languages like python, and is why python is so fantastically successful: there is a huge amount of code that other people have written that you can reuse!)\n", + "\n", + "3. Weeks, months, or even years later, you might want to go back and re-use your own code. If your code not written in a clear and understandable way, you yourself will waste a lot of time trying to figure out what you did!\n", + "\n", + "In preparing the lecture notebooks for this course, I personally experienced 3 myself: I went back to some simple code that I wrote a few weeks earlier, and I realized I had no idea what the code did!\n", + "\n", + "So how do you make sure that your code is understandable? There are two practices that can make this useful:\n", + "\n", + "* **Meaningful variable names and logical structure**\n", + "* **Comments explaining what you're doing**\n", + "\n", + "As a concrete example, I will take the following code below and show how by using these two techniques, an imcomprehensible piece of code that is them modified in two steps to be very easy to understand.\n", + "\n", + "### BAD code\n", + "\n", + "Here is the \"bad\" code:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "foo3 = 2.0; foo1 = 10; foo2 = 0.0\n", + "def foo6(foo1):\n", + " return foo1**2\n", + "foo5 = 0.5*foo6(foo2) + foo6(foo3); foo4 = (foo3-foo2)/foo1\n", + "for foo7 in range(1,foo1):\n", + " foo5 += foo6(foo2+foo7*foo4)\n", + "foo83 = (foo5*foo4)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2e974ec571fecfeb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "Can you figure out what this code does? It would take me personally a lot of time...\n", + "\n", + "This code is a mess, but is technically correct: it will give the right answer. But correct code can also be terrible code, and for me, the code above is really terrible!" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e7549dc7264de2b1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "### Better variable names and logical structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def f(x):\n", + " return x**4 - 2*x + 1\n", + "\n", + "N = 10\n", + "a = 0.0\n", + "b = 2.0\n", + "h = (b-a)/N\n", + "\n", + "s = 0.5*f(a) + 0.5*f(b)\n", + "for k in range(1,N):\n", + " s += f(a+k*h)\n", + "\n", + "answer2 = (h*s)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-92d6d0c264c6fcf7", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "Why is this better? \n", + "\n", + "* Not all variables and functions are named `foo`, but have somewhat meaningful names like `f(x)`, `a`, `b`, `N`\n", + "* The definition of the variables is in a logical order (a is after b, for example)\n", + "* The functions are defined at the top (a common convention to make your code understandable)\n", + "* There are blank lines separating different logical parts of the code:\n", + " * function definitions at the top\n", + " * then the defining the values of the input variables\n", + " * then the loop that does the actual work\n", + " * then the definition of the answer / output\n", + "\n", + "When you work on the Integration notebook, you may also recognise this from the trapezoidal technique. The variable names also match the formulas from a textbook we have used in the past, and if you had just read the textbook section on Integration, you will also probably almost instantly recognize all the variable names from those used in the derivation from the book, and also what it does. \n", + "\n", + "However, if you come back at a later time when the integration section of the textbook is not so fresh in your head (as I did recently while updating this notebook), you may immediately think \"what was I doing here?\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1c97306af1ed43bd", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "### Even better: Descriptive variable names\n", + "\n", + "The above code is already better. And if I am looking a textbook where all the things above are clearly explained, then maybe the meaning of the parameters `a`, `b`, `N` etc are clear to me. However, if you haven't seen the code before, it may not be immediately obvious. Are `a` and `b` the slope and intercept of a line $y=ax+b$? Or are they something else? Is `N` the number of points in my discretisation, or is it the number of slices in my integral? \n", + "\n", + "For this reason, it is better to use descriptive variable names that themselves already describe what the meaning of the variable is:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def f(x):\n", + " return x**4 - 2*x + 1\n", + "\n", + "N_slices = 10\n", + "start = 0.0\n", + "stop = 2.0\n", + "step_size = (stop-start)/N_slices\n", + "\n", + "running_sum = 0.5*f(start) + 0.5*f(stop)\n", + "\n", + "for i in range(1,N_slices):\n", + " running_sum += f(start+i*step_size)\n", + "\n", + "answer2 = (step_size*running_sum)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7addaef0a9d5bcc0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "This is a bit more typing, but you can also use \"tab completion\" to save typing: just type the first few letters, push the \"tab\" key, and the Jupyter notebook will automatically type the rest out for you." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2bf3b2cf0b84fa1e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "### Commenting your code\n", + "\n", + "By taking some time to explain what you're doing and why, this code can now become instantly understandable:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Code for integration with the Trapezoidal rule\n", + "\n", + "# The function we want to integrate\n", + "def f(x):\n", + " return x**4 - 2*x + 1\n", + "\n", + "# The number of slices we will use, and the starting and end points of the integral\n", + "# Note that this is NOT the number of points for our discritisation: that is N_slices+1.\n", + "N_slices = 10\n", + "\n", + "# The starting and stopping points of our integration range\n", + "start = 0.0\n", + "stop = 2.0\n", + "\n", + "# The step size\n", + "step_size = (stop-start)/N_slices\n", + "\n", + "# A running sum we will use\n", + "# We start by the half of the start and end points (trapezoidal rule)\n", + "running_sum = 0.5*f(stop) + 0.5*f(stop)\n", + "\n", + "# Now a for loop that does the sum.\n", + "# range(1,N_slices) will give us 9 numbers running from 1 to N_slices-1 \n", + "# For N_slices=10, we will get a list of 9 numbers: [1,2,3,4,5,6,7,8,9]\n", + "# Note that we do not need i = 0 because in the trapezoidal rule, we add it\n", + "# separately above with a factor of 0.5, together with the end point\n", + "for i in range(1,N_slices):\n", + " running_sum += f(start+i*step_size)\n", + "\n", + "# The integral is then given by the produce of the sum and the step siz\n", + "answer2 = (running_sum*step_size)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-003fff51c9b847a8", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "Here, we explain what we are doing and why! This code is likely understandable by anyone who reads it, including myself again in a year's time.\n", + "\n", + "In particular, we can point out some of the sneaky things, such as the difference between number of slices and number of points, and also why our `range()` function starts at 1 and not 0. \n", + "\n", + "It is always a good idea to add comments explaning what you do. But, of course, you don't want to write code that is more comments than actual code! Where do I draw the line? When do I decide if I should add a comment of if I think it is already clear enough without it? \n", + "\n", + "**Good guideline:** if there is something that you had to fiddle around with for a while to get the code correct, add a comment in your code so that you remember this and others get to learn from your hard work!\n", + "\n", + "Using descriptive variable names also helps, as then you may not have to add a comment where you otherwise would. \n", + "\n", + "And, while it takes some time while you're doing it, but adding detailed comments to our code will make your life MUCH easier in the future, will make your code understandable for others, and will maximize your grade :)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9be17b61cca35905", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "### Summary: Writing understandable code\n", + "\n", + "In summary, you can make your code much more understandable for others if you:\n", + "\n", + "1. Use descriptive variable names\n", + "2. Explain things in comments" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-56f4929b4961e190", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "## Avoid hard-coding\n", + "\n", + "\n", + "\"Hard coding\" is one of the coding practices we will be discouraging in this course, and the use of \"hard coding\" can lose you points in your final exam.\n", + "\n", + "What is \"hard coding\"?\n", + "\n", + "Hard coding is when you fill in values repeated at multiple places in your code. For example, say your are asked to make an array `x` that is 1000 points and runs from 0 to 10, calculate array $y = \\sin(x)$, and then print out the percentage of points in `y` that have a value of less than 0.3. In the code snippets below, we will take a look at a **BAD** way of doing this with hard-coding, and then also proper ways of doing this that do not involve hard-coding." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-aa2fa4efb0e5564c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "### BAD code with hard-coding\n", + "\n", + "Here is an example of a **BAD** piece of code that does this using hard-coding of the number of points:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "# An example of hard coding (DO NOT DO!!!!)\n", + "x = np.linspace(0,10,1000)\n", + "y = np.sin(x)\n", + "\n", + "num = 0\n", + "for i in range(1000):\n", + " if (y[i]<0.3):\n", + " num += 1\n", + "\n", + "print(\"The percentange of points in y less than 0.3 is %.2f %%\" % (num/1000*100))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-fbcafee7a6cd637d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "In this example code, we say that we have \"hard coded\" the number of points in the array. Say we wanted to then change the number of points in array `x` from 1000 to 1500: we would then have to go through all of our code by hand and change `1000` each time into `1500`. \n", + "\n", + "This time that is not so difficult since it is a short piece of code, but when you build more complex programs, this will become a lot more work, and the chance of getting a bug in your code will increase exponentially!\n", + "\n", + "### GOOD example with no hard-coding: replacing hard coded numbers with a variable\n", + "\n", + "Below, instead of typing in `1000` each time manually, we will define a variable at the top that will define how many points `x` has. This way, if we want to change the size of `x`, all we have to do is change this variable and everything will still work." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# An example THE PROPER WAY, using a variable\n", + "npts = 1000\n", + "x = np.linspace(0,10,npts)\n", + "y = np.sin(x)\n", + "maxval=0.3\n", + "\n", + "num = 0\n", + "for i in range(npts):\n", + " if (y[i]<maxval):\n", + " num += 1\n", + "\n", + "print(\"The percentange of points in y less than 0.3 is %.2f %%\" % (num/npts*100))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1efdf84ecbc4b32b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "The big advantage here is that you can just change `npts` and all the code works immediately with no further changes!" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-03279826167e04d9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "### Another GOOD option: replace hard coded numbers with automatically calculated values\n", + "\n", + "Here below is also another option: instead of using a variable `npts`, we can also use the `len()` function to automatically calculate the length of array x:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# An example using len()\n", + "npts=1000\n", + "x = np.linspace(0,10,npts)\n", + "y = np.sin(x)\n", + "maxval=0.3\n", + "num = 0\n", + "\n", + "# For for loops, this is quite handy and easy to read\n", + "for i in range(len(y)):\n", + " if (y[i]<maxval):\n", + " num += 1\n", + "\n", + "# However, for this line, I find the example above with npts a bit more readable: it is very obvious\n", + "# why I would divide by npts, but maybe not so immediately obvious why dividing by len(y)\n", + "# is the right thing to do...but it's fine, in particular if you add a comment explaining\n", + "# yourself.\n", + "print(\"The percentange of points in y less than 0.3 is %.2f %%\" % (num/len(y)*100))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b431872cbd0d20c0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "As mentioned in the code, this is quite common and probably always a good idea for the for loop: this way, you never accidentally loop over the end of the array! \n", + "\n", + "(In python, this will either give an error, or, in the case of slicing, will give strange results since slicing applies \"periodic\" boundary conditions when indexing instead of giving an error...)\n", + "\n", + "In the case of calculating the percentage, it is maybe a bit less obvious to a non-trained programmer that this is the right thing to do, in which case it is a good idea to add a comment to your code explaining what you're doing.\n", + "\n", + "### Summary: How to avoid hard coding\n", + "\n", + "You will make your code much more robust and maintainable by avoiding hard coding of values if you:\n", + "\n", + "* Replace hard-coded values with variables\n", + "* Use functions that can automatically determine the appropriate value to use" + ] + } + ], + "metadata": { + "jupytext": { + "formats": "ipynb,md" + }, + "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.7.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": true, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Good Programming Practices/Good coding practices.md b/Good Programming Practices/Good coding practices.md new file mode 100644 index 0000000000000000000000000000000000000000..3f80c623d883b28bfaf862fd89353f2c090270ea --- /dev/null +++ b/Good Programming Practices/Good coding practices.md @@ -0,0 +1,304 @@ +--- +jupyter: + jupytext: + formats: ipynb,md + text_representation: + extension: .md + format_name: markdown + format_version: '1.2' + jupytext_version: 1.3.0 + kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Good coding practices + +In this notebook, we outline some example of "good coding practices": things you should do to make your code robust and understandable to others. + +Learning objectives: + +* Student is able to write code that has a clear and understandable structure +* Student is able to write code with descriptive variable names +* Student is able to write code with explanatory comments +* Student is able to write code that avoids "hard coding" + + +<!-- #region nbgrader={"grade": false, "grade_id": "cell-f0a26f3edf642ded", "locked": true, "schema_version": 3, "solution": false, "task": false} --> +## Writing understandable code + +Once you become an expert at coding, it will become very easy to write code that python can understand. + +What is actually a big challenge in computer science is actually writing code that works, that is efficient, and most importantly, that **other people will understand!** + +You might think: but I'm writing this code only for myself, so I don't really care about if my code is easy to understand, right? + +There are many reasons why this is incorrect: + +1. In this course, your teachers and your TAs will need to understand your code in order to grade it. If we can't quickly understand what you have done, **this will affect your grade!!!** (One of our grading criteria will be the clarity and readability of your code.) + +2. You might, in the future, want to share your code with someone else. If they have to spend a lot of time figuring out what you've done, then your code is useless to them. (In fact, this is the power of open languages like python, and is why python is so fantastically successful: there is a huge amount of code that other people have written that you can reuse!) + +3. Weeks, months, or even years later, you might want to go back and re-use your own code. If your code not written in a clear and understandable way, you yourself will waste a lot of time trying to figure out what you did! + +In preparing the lecture notebooks for this course, I personally experienced 3 myself: I went back to some simple code that I wrote a few weeks earlier, and I realized I had no idea what the code did! + +So how do you make sure that your code is understandable? There are two practices that can make this useful: + +* **Meaningful variable names and logical structure** +* **Comments explaining what you're doing** + +As a concrete example, I will take the following code below and show how by using these two techniques, an imcomprehensible piece of code that is them modified in two steps to be very easy to understand. + +### BAD code + +Here is the "bad" code: +<!-- #endregion --> + +```python +foo3 = 2.0; foo1 = 10; foo2 = 0.0 +def foo6(foo1): + return foo1**2 +foo5 = 0.5*foo6(foo2) + foo6(foo3); foo4 = (foo3-foo2)/foo1 +for foo7 in range(1,foo1): + foo5 += foo6(foo2+foo7*foo4) +foo83 = (foo5*foo4) +``` + +<!-- #region nbgrader={"grade": false, "grade_id": "cell-2e974ec571fecfeb", "locked": true, "schema_version": 3, "solution": false, "task": false} --> +Can you figure out what this code does? It would take me personally a lot of time... + +This code is a mess, but is technically correct: it will give the right answer. But correct code can also be terrible code, and for me, the code above is really terrible! +<!-- #endregion --> + +<!-- #region nbgrader={"grade": false, "grade_id": "cell-e7549dc7264de2b1", "locked": true, "schema_version": 3, "solution": false, "task": false} --> +### Better variable names and logical structure +<!-- #endregion --> + +```python +def f(x): + return x**4 - 2*x + 1 + +N = 10 +a = 0.0 +b = 2.0 +h = (b-a)/N + +s = 0.5*f(a) + 0.5*f(b) +for k in range(1,N): + s += f(a+k*h) + +answer2 = (h*s) +``` + +<!-- #region nbgrader={"grade": false, "grade_id": "cell-92d6d0c264c6fcf7", "locked": true, "schema_version": 3, "solution": false, "task": false} --> +Why is this better? + +* Not all variables and functions are named `foo`, but have somewhat meaningful names like `f(x)`, `a`, `b`, `N` +* The definition of the variables is in a logical order (a is after b, for example) +* The functions are defined at the top (a common convention to make your code understandable) +* There are blank lines separating different logical parts of the code: + * function definitions at the top + * then the defining the values of the input variables + * then the loop that does the actual work + * then the definition of the answer / output + +When you work on the Integration notebook, you may also recognise this from the trapezoidal technique. The variable names also match the formulas from a textbook we have used in the past, and if you had just read the textbook section on Integration, you will also probably almost instantly recognize all the variable names from those used in the derivation from the book, and also what it does. + +However, if you come back at a later time when the integration section of the textbook is not so fresh in your head (as I did recently while updating this notebook), you may immediately think "what was I doing here?" +<!-- #endregion --> + +<!-- #region nbgrader={"grade": false, "grade_id": "cell-1c97306af1ed43bd", "locked": true, "schema_version": 3, "solution": false, "task": false} --> +### Even better: Descriptive variable names + +The above code is already better. And if I am looking a textbook where all the things above are clearly explained, then maybe the meaning of the parameters `a`, `b`, `N` etc are clear to me. However, if you haven't seen the code before, it may not be immediately obvious. Are `a` and `b` the slope and intercept of a line $y=ax+b$? Or are they something else? Is `N` the number of points in my discretisation, or is it the number of slices in my integral? + +For this reason, it is better to use descriptive variable names that themselves already describe what the meaning of the variable is: +<!-- #endregion --> + +```python +def f(x): + return x**4 - 2*x + 1 + +N_slices = 10 +start = 0.0 +stop = 2.0 +step_size = (stop-start)/N_slices + +running_sum = 0.5*f(start) + 0.5*f(stop) + +for i in range(1,N_slices): + running_sum += f(start+i*step_size) + +answer2 = (step_size*running_sum) +``` + +<!-- #region nbgrader={"grade": false, "grade_id": "cell-7addaef0a9d5bcc0", "locked": true, "schema_version": 3, "solution": false, "task": false} --> +This is a bit more typing, but you can also use "tab completion" to save typing: just type the first few letters, push the "tab" key, and the Jupyter notebook will automatically type the rest out for you. +<!-- #endregion --> + +<!-- #region nbgrader={"grade": false, "grade_id": "cell-2bf3b2cf0b84fa1e", "locked": true, "schema_version": 3, "solution": false, "task": false} --> +### Commenting your code + +By taking some time to explain what you're doing and why, this code can now become instantly understandable: +<!-- #endregion --> + +```python +# Code for integration with the Trapezoidal rule + +# The function we want to integrate +def f(x): + return x**4 - 2*x + 1 + +# The number of slices we will use, and the starting and end points of the integral +# Note that this is NOT the number of points for our discritisation: that is N_slices+1. +N_slices = 10 + +# The starting and stopping points of our integration range +start = 0.0 +stop = 2.0 + +# The step size +step_size = (stop-start)/N_slices + +# A running sum we will use +# We start by the half of the start and end points (trapezoidal rule) +running_sum = 0.5*f(stop) + 0.5*f(stop) + +# Now a for loop that does the sum. +# range(1,N_slices) will give us 9 numbers running from 1 to N_slices-1 +# For N_slices=10, we will get a list of 9 numbers: [1,2,3,4,5,6,7,8,9] +# Note that we do not need i = 0 because in the trapezoidal rule, we add it +# separately above with a factor of 0.5, together with the end point +for i in range(1,N_slices): + running_sum += f(start+i*step_size) + +# The integral is then given by the produce of the sum and the step siz +answer2 = (running_sum*step_size) +``` + +<!-- #region nbgrader={"grade": false, "grade_id": "cell-003fff51c9b847a8", "locked": true, "schema_version": 3, "solution": false, "task": false} --> +Here, we explain what we are doing and why! This code is likely understandable by anyone who reads it, including myself again in a year's time. + +In particular, we can point out some of the sneaky things, such as the difference between number of slices and number of points, and also why our `range()` function starts at 1 and not 0. + +It is always a good idea to add comments explaning what you do. But, of course, you don't want to write code that is more comments than actual code! Where do I draw the line? When do I decide if I should add a comment of if I think it is already clear enough without it? + +**Good guideline:** if there is something that you had to fiddle around with for a while to get the code correct, add a comment in your code so that you remember this and others get to learn from your hard work! + +Using descriptive variable names also helps, as then you may not have to add a comment where you otherwise would. + +And, while it takes some time while you're doing it, but adding detailed comments to our code will make your life MUCH easier in the future, will make your code understandable for others, and will maximize your grade :) +<!-- #endregion --> + +<!-- #region nbgrader={"grade": false, "grade_id": "cell-9be17b61cca35905", "locked": true, "schema_version": 3, "solution": false, "task": false} --> +### Summary: Writing understandable code + +In summary, you can make your code much more understandable for others if you: + +1. Use descriptive variable names +2. Explain things in comments +<!-- #endregion --> + +<!-- #region nbgrader={"grade": false, "grade_id": "cell-56f4929b4961e190", "locked": true, "schema_version": 3, "solution": false, "task": false} --> +## Avoid hard-coding + + +"Hard coding" is one of the coding practices we will be discouraging in this course, and the use of "hard coding" can lose you points in your final exam. + +What is "hard coding"? + +Hard coding is when you fill in values repeated at multiple places in your code. For example, say your are asked to make an array `x` that is 1000 points and runs from 0 to 10, calculate array $y = \sin(x)$, and then print out the percentage of points in `y` that have a value of less than 0.3. In the code snippets below, we will take a look at a **BAD** way of doing this with hard-coding, and then also proper ways of doing this that do not involve hard-coding. +<!-- #endregion --> + +<!-- #region nbgrader={"grade": false, "grade_id": "cell-aa2fa4efb0e5564c", "locked": true, "schema_version": 3, "solution": false, "task": false} --> +### BAD code with hard-coding + +Here is an example of a **BAD** piece of code that does this using hard-coding of the number of points: +<!-- #endregion --> + +```python +import numpy as np + +# An example of hard coding (DO NOT DO!!!!) +x = np.linspace(0,10,1000) +y = np.sin(x) + +num = 0 +for i in range(1000): + if (y[i]<0.3): + num += 1 + +print("The percentange of points in y less than 0.3 is %.2f %%" % (num/1000*100)) +``` + +<!-- #region nbgrader={"grade": false, "grade_id": "cell-fbcafee7a6cd637d", "locked": true, "schema_version": 3, "solution": false, "task": false} --> +In this example code, we say that we have "hard coded" the number of points in the array. Say we wanted to then change the number of points in array `x` from 1000 to 1500: we would then have to go through all of our code by hand and change `1000` each time into `1500`. + +This time that is not so difficult since it is a short piece of code, but when you build more complex programs, this will become a lot more work, and the chance of getting a bug in your code will increase exponentially! + +### GOOD example with no hard-coding: replacing hard coded numbers with a variable + +Below, instead of typing in `1000` each time manually, we will define a variable at the top that will define how many points `x` has. This way, if we want to change the size of `x`, all we have to do is change this variable and everything will still work. +<!-- #endregion --> + +```python +# An example THE PROPER WAY, using a variable +npts = 1000 +x = np.linspace(0,10,npts) +y = np.sin(x) +maxval=0.3 + +num = 0 +for i in range(npts): + if (y[i]<maxval): + num += 1 + +print("The percentange of points in y less than 0.3 is %.2f %%" % (num/npts*100)) +``` + +<!-- #region nbgrader={"grade": false, "grade_id": "cell-1efdf84ecbc4b32b", "locked": true, "schema_version": 3, "solution": false, "task": false} --> +The big advantage here is that you can just change `npts` and all the code works immediately with no further changes! +<!-- #endregion --> + +<!-- #region nbgrader={"grade": false, "grade_id": "cell-03279826167e04d9", "locked": true, "schema_version": 3, "solution": false, "task": false} --> +### Another GOOD option: replace hard coded numbers with automatically calculated values + +Here below is also another option: instead of using a variable `npts`, we can also use the `len()` function to automatically calculate the length of array x: +<!-- #endregion --> + +```python +# An example using len() +npts=1000 +x = np.linspace(0,10,npts) +y = np.sin(x) +maxval=0.3 +num = 0 + +# For for loops, this is quite handy and easy to read +for i in range(len(y)): + if (y[i]<maxval): + num += 1 + +# However, for this line, I find the example above with npts a bit more readable: it is very obvious +# why I would divide by npts, but maybe not so immediately obvious why dividing by len(y) +# is the right thing to do...but it's fine, in particular if you add a comment explaining +# yourself. +print("The percentange of points in y less than 0.3 is %.2f %%" % (num/len(y)*100)) +``` + +<!-- #region nbgrader={"grade": false, "grade_id": "cell-b431872cbd0d20c0", "locked": true, "schema_version": 3, "solution": false, "task": false} --> +As mentioned in the code, this is quite common and probably always a good idea for the for loop: this way, you never accidentally loop over the end of the array! + +(In python, this will either give an error, or, in the case of slicing, will give strange results since slicing applies "periodic" boundary conditions when indexing instead of giving an error...) + +In the case of calculating the percentage, it is maybe a bit less obvious to a non-trained programmer that this is the right thing to do, in which case it is a good idea to add a comment to your code explaining what you're doing. + +### Summary: How to avoid hard coding + +You will make your code much more robust and maintainable by avoiding hard coding of values if you: + +* Replace hard-coded values with variables +* Use functions that can automatically determine the appropriate value to use +<!-- #endregion --> diff --git a/Notebook 1/Notebook_1_Python_Basics.ipynb b/Notebook 1/Notebook 1 Python Basics.ipynb similarity index 64% rename from Notebook 1/Notebook_1_Python_Basics.ipynb rename to Notebook 1/Notebook 1 Python Basics.ipynb index 767da0dad2dfe89dcd9b8de6920cc922a94c0e1f..ea17aec7a6947f86fc9f7b5e676bd88531c7533e 100644 --- a/Notebook 1/Notebook_1_Python_Basics.ipynb +++ b/Notebook 1/Notebook 1 Python Basics.ipynb @@ -2,7 +2,16 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e68e92ac1082dd04", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "# Python Basics\n", "\n", @@ -27,7 +36,7 @@ "\n", "Every time you run python, either by running it on the command line, or by running it in a Jupyter notebook like you are now, a python **\"kernel\"** is created. This kernel is a copy of the python program (\"interpreter\") that runs continuously on your computer (until you stop it).\n", "\n", - "Jupyter notebooks are a way to interact with the python kernel. Notebooks are divided up into \"cells\", which can be either a text (<a href=https://en.wikipedia.org/wiki/Markdown>markdown</a> format) cell, like this one, or a code cell (containing your code), like the cell below it. \n", + "Jupyter notebooks are a way to interact with the python kernel. Notebooks are divided up into \"cells\", which can be either a text cell (in a special text formatting language called <a href=https://en.wikipedia.org/wiki/Markdown>markdown</a>), like the one you are reading now. There are also code cell (containing your code), like the cell below it. \n", "\n", "The selected cell is surrounded by a box. If you type \"enter\" in a text cell you can start editing the cell. If you push \"Run\" above, or type \"Shift-Enter\", the code will be \"run\". If it is a code cell, it will run a command (see below). If it is a markdown cell, it will \"compile\" the markdown text language into formatted (HTML) text. \n", "\n", @@ -36,24 +45,25 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Hello world\n" - ] - } - ], + "outputs": [], "source": [ "print(\"Hello world\")" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ee6ac0827c6a6783", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "To send this command to the python kernel, there are several options. First, select the cell (so that it is either blue or green), and then:\n", "\n", @@ -63,7 +73,7 @@ "\n", "When you run the cell, the code will be sent to the python kernel, which will translate your python command into a binary language your computer CPU understands, send it to the CPU, and read back the answer. If the code you run produces an \"output\", meaning that the kernel will send something back to you, then the output that your code produces will be displayed below the code cell in the \"output\" section of the code cell. This is a scheme of what this process looks like \"behind the scenes\": \n", "\n", - "<img width=60% src=\"behind_the_scenes.png\"></img>\n", + "<img width=60% src=\"resource/asnlib/public/Notebook_1_behind_the_scenes.png\"></img>\n", "\n", "After you have run the code cell, a number will appear beside your code cell. This number tell you in which order that piece of code was sent to the kernel. Because the kernel has a \"memory\", as you will see in the next section, this number can be useful so that you remember in which order the code cells in your notebook were executed. \n", "\n", @@ -72,19 +82,9 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Hello\n", - "world\n", - "Goodbye\n" - ] - } - ], + "outputs": [], "source": [ "print(\"Hello\")\n", "print(\"world\")\n", @@ -93,24 +93,25 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8698bcb51f5ce066", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In the above, the text in the code cell are all python commands. In addition, if you start a line in a code cell with a `#`, python will ignore this line of text. This is use to add **comments** to your code. It is good programming practice to use comments to explain what the code is doing:" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "This is a message\n" - ] - } - ], + "outputs": [], "source": [ "# This will print out a message\n", "print(\"This is a message\")" @@ -118,34 +119,45 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4919a733b4210f11", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.1** Print your own string to the command line. Can you use special characters as well?" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " df# \n" - ] - } - ], + "outputs": [], "source": [ - "# Your code here\n", - "print(\" df# \")" + "### BEGIN SOLUTION\n", + "print(\" df# \")\n", + "### END SOLUTION" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-74c3c19c58006da4", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## The Python kernel has a memory\n", "\n", @@ -154,18 +166,25 @@ }, { "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": true - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ - "a = 5\n" + "a = 5" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-84c1e3509c93658b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In python, the `=` symbol represents the **assignment operator**: it is an instruction to **assign** the value of `5` to the variable `a`. If variable `a` already exists, it will be over-written with the new value (in fact, `a` is a python object, something that we will explain in the optional notebook in more detail). If variable `a` does not yet exist, then python will create a new variable for you automatically.\n", "\n", @@ -174,102 +193,109 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "5\n" - ] - } - ], + "outputs": [], "source": [ "print(a)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-99b80d998fcc74ff", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ - "Note that we can also add a message to this by combining this with a message with the `print()` statement by combining things with commas:" + "Besides numerical values variables can also be strings, which are sequences of characters. You make a string by putting the text between quotes.\n", + "\n", + "Note that we can also add a message if we add a string and a numerical value in the `print()` statement by combining things with commas:" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The value of a is 5\n" - ] - } - ], + "outputs": [], "source": [ "print(\"The value of a is\",a)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a4382a92b6e9aeec", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ - "**Exercise 1.2** Combine a string, a variable, and a numerical values in a single `print` statement using the `,` separator." + "**Exercise 1.2** Combine multiple strings and numerical values in a single `print` statement using the `,` separator." ] }, { "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "print this word: 4\n" - ] + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2020-08-21T14:33:20.498051Z", + "start_time": "2020-08-21T14:33:20.472191Z" } - ], + }, + "outputs": [], "source": [ - "# Your code here\n", - "word = 4\n", - "print(\"print this word:\", word)\n", - "\n" + "# your code here" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-cbf03d005dae5d5d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.3** Change the value of `a` to 7 by executing the following cell, and then re-run the **above** cell containing the command `print(a)` (the one with output `5`). What value gets printed now in that cell? " ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "7\n" - ] - } - ], + "outputs": [], "source": [ - "a = 7\n", - "print(a)" + "# your code here to assign the value 7 to variable a" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2b713ce46a7bca48", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "As you can see in notebooks that the location of your code doesn’t matter, but the order in which you execute them does!!\n", "\n", @@ -278,18 +304,9 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0\n", - "7\n" - ] - } - ], + "outputs": [], "source": [ "b = 0\n", "print(b)\n", @@ -299,28 +316,25 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-16240c968680ffa4", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Sometimes, if you execute a lot of cells, or maybe even re-execute a cell after changing its contents, you might lose track of what variables are defined in the memory of your python kernel. For this, there is a convenient built-in \"magic\" command called `%whos` that can list for you all the variables that have been defined in your kernel, along with their values:" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Variable Type Data/Info\n", - "----------------------------\n", - "a int 5\n", - "b int 7\n", - "word int 4\n" - ] - } - ], + "outputs": [], "source": [ "a=5 \n", "%whos" @@ -328,7 +342,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d61c3a57fbd93ed6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "_(Some notes about `%whos`: `%whos` is not a \"native\" command of the python language, but instead a \"built-in\" command that has been added by the creators of Jupyter. Because of this, you cannot use it outside of Jupyter / iPython...)_\n", "\n", @@ -337,10 +360,8 @@ }, { "cell_type": "code", - "execution_count": 18, - "metadata": { - "collapsed": true - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ "c = 10\n", @@ -349,37 +370,41 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Variable Type Data/Info\n", - "-----------------------------\n", - "a int 5\n", - "b int 7\n", - "c int 10\n", - "d float 15.5\n", - "word int 4\n" - ] - } - ], + "outputs": [], "source": [ "%whos" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-bea496517f99df02", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In this case the variable named is displayed, its value, but also its type. Type defines the format in which a variable is stored in memory. In this case `int` stands for integer and `float` stands for floating point number, which is the usual way in which real numbers are stored in a computer. We will learn more about Python variable types below." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-80d1461986f4d365", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Starting and stopping the kernel\n", "\n", @@ -400,6 +425,9 @@ "* \"Stop\": tells the kernel to abort trying to run the code it is working on, but does not erase its memory\n", "* \"Restart\": \"kill\" the kernel (erasing its memory), and start a new one attached to the notebook.\n", "\n", + "<img src=\"resource/asnlib/public/Notebook_1_stop_button.png\" width=20%></img>\n", + "<img src=\"resource/asnlib/public/Notebook_1_restartkernelmenu.png\" width=60%></img>\n", + "\n", "To see this in action, you can execute the following cell, which will do nothing other than wait for 10 minutes:" ] }, @@ -407,7 +435,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, "scrolled": false }, "outputs": [], @@ -418,7 +445,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6c0352859b13ed82", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You will notice that while a cell is running, the text beside it shows `In [*]:`. The `*` indicates that the cell is being executed, and will change to a number when the cell is finished. You will also see that the small circle beside the `Python 3` text on the right side of the Jupyter menu bar at the top of the page will become solid. Unless you have a lot of patience, you should probably stop the kernel, using the \"Stop\" button, or the menu item \"Kernel / Interrupt\".\n", "\n", @@ -427,31 +463,27 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "5\n", - "Variable Type Data/Info\n", - "----------------------------\n", - "a int 5\n" - ] - } - ], + "outputs": [], "source": [ - "# Your code here\n", - "print(a)\n", - "%whos" + "# add your code to exectue the command %whos here, then restart the kernel and run this cell again" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-fd74d0d65cf02f0a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Python variable types\n", "\n", @@ -464,7 +496,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, "scrolled": true }, "outputs": [], @@ -474,7 +505,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6f2bfadc72282277", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In the second column, you can see the **type** that python chose for the variables we created. `int` corresponds to integer numbers, `float` corresponds to floating-point numbers. You can see that for variable `c`, python had to choose a `float` type (because 15.5 is not an integer), but for `a` and `b`, it chose integer types. \n", "\n", @@ -485,10 +525,8 @@ }, { "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": true - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ "a = a/2" @@ -496,28 +534,27 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Variable Type Data/Info\n", - "-----------------------------\n", - "a float 2.5\n" - ] - } - ], + "outputs": [], "source": [ "%whos" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8ac075dfca80d989", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Because 5/2 = 2.5, Python decided to change the type of variable `a` from `int` to `float` after the assignment operation `a = a/2`. \n", "\n", @@ -526,10 +563,8 @@ }, { "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": true - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ "c = 1.5e-8" @@ -537,7 +572,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9f4aa0b51687698a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "The notation `1.5e-8` is a notation used in python to indicate the number $1.5 \\times 10^{-8}$.\n", "\n", @@ -546,10 +590,8 @@ }, { "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": true - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ "d = 1+1j" @@ -557,65 +599,63 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Variable Type Data/Info\n", - "-------------------------------\n", - "a float 2.5\n", - "c float 1.5e-08\n", - "d complex (1+1j)\n" - ] - } - ], + "outputs": [], "source": [ "%whos" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-212eb128ab19078d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "The notation `1j` is special, in particular because there is **no space** between the number `1` and the `j`. This is how Python knows that you are telling it to make a complex number (and not just referring to a variable named `j`...). The number in front of the `j` can be any floating point number: for example," ] }, { "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(0.5+0.5j)" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2020-08-21T14:57:37.501905Z", + "start_time": "2020-08-21T14:57:37.486365Z" } - ], + }, + "outputs": [], "source": [ - "0.5*d" + "0.5j" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-770f5a2d267d39b4", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In addition to the mathematical variable types listed above, there are also other types of variables in Python. A common one you may encounter is the \"string\" variable type `str`, which is used for pieces of text. To tell Python you want to make a string, you enclose the text of your string in either single forward quotes `'` or double forward quotes `\"`:" ] }, { "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": true - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ "e = \"This is a string\"\n", @@ -624,25 +664,9 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Variable Type Data/Info\n", - "-------------------------------\n", - "a float 2.5\n", - "c float 1.5e-08\n", - "d complex (1+1j)\n", - "e str This is a string\n", - "f str This is also a string\n", - "This is a string\n", - "This is also a string\n" - ] - } - ], + "outputs": [], "source": [ "%whos\n", "print(e)\n", @@ -651,7 +675,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6e76757478040b18", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can also make multiline strings using three single quotes:" ] @@ -659,9 +692,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "multi = \\\n", @@ -675,7 +706,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-31e4b55de81cef07", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note here that I have used a backslash: this a way to split Python code across multiple lines. \n", "\n", @@ -686,37 +726,35 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "This is a string This is also a string\n" - ] - } - ], + "outputs": [], "source": [ - "# Your code here\n", - "print(e+\" \"+f)" + "# Your code here" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1fa7be3de8eb7f5a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "There is one more useful variable type we will introduce here: the \"boolean\" type `bool`. Boolean variable can have two values: `True` and `False`. You type them in directly as `True` and `False` with no quotes (you will see them turn green). " ] }, { "cell_type": "code", - "execution_count": 21, - "metadata": { - "collapsed": true - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ "g = 0" @@ -724,53 +762,40 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "metadata": { "scrolled": false }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Variable Type Data/Info\n", - "-------------------------------\n", - "a float 2.5\n", - "c float 1.5e-08\n", - "d complex (1+1j)\n", - "e str This is a string\n", - "f str This is also a string\n", - "g int 0\n" - ] - } - ], + "outputs": [], "source": [ "%whos" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-12ff71b21d9d01e3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ - "We will use boolean types much more later when we look at program control flow, but a simple example is the `if` statement:" + "We will use boolean types much more later when we look at program control flow, but a simple example using the `if` statement is given below: \n", + "\n", + "No panic if you don't yet understand the if statement, there will be another entire notebook dedicated to them. This is just an example of why boolean variables exist." ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "True is always true.\n", - "g is not true!\n" - ] - } - ], + "outputs": [], "source": [ "if True:\n", " print(\"True is always true.\")\n", @@ -784,7 +809,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7f2acb9e44b581d8", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can try changing the value of `g` above to `False` and see what happens if you run the above code cell again.\n", "\n", @@ -793,14 +827,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4016c53a455f3656", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.6** Discover which numbers can be used as `True` and `False` in Python by changing the value of `g` above and re-running the cells." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c1323d47dae023f1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Converting variables between different types\n", "\n", @@ -809,64 +861,43 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "5.0" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "float(5)" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "7" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "int(7.63)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e7f8cf2c018ef4fd", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note that when converting an `float` to an `int`, python does not round off the value, but instead drops all the numbers off after the decimal point (it \"trucates\" it). If we want to convert to an integer and round it off, we can use the `round()` function:" ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "8\n" - ] - } - ], + "outputs": [], "source": [ "b = round(7.63)\n", "print(b)" @@ -874,17 +905,9 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "8.4\n" - ] - } - ], + "outputs": [], "source": [ "type(b)\n", "print(b+0.4)" @@ -892,7 +915,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f3f5149b2d627e26", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "This works for conversions between many types. Sometimes, you have will lose information in this process: for example, converting a `float` to an `int`, we lose all the numbers after the decimal point. In this example, Python makes a guess at what you probably want to do, and decides to round off the floating point number to the nearest integer. \n", "\n", @@ -901,56 +933,36 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "can't convert complex to float", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m<ipython-input-29-215f213c112b>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mfloat\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m+\u001b[0m\u001b[1;36m1j\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m: can't convert complex to float" - ] - } - ], + "outputs": [], "source": [ "float(1+1j)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-582c3f589fac8746", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "A very useful feature is that Python can convert numbers into strings:" ] }, { "cell_type": "code", - "execution_count": 32, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "8.54\n", - "Variable Type Data/Info\n", - "-------------------------------\n", - "a float 7.54\n", - "b float 8.54\n", - "c float 1.5e-08\n", - "d complex (1+1j)\n", - "e str This is a string\n", - "f str This is also a string\n", - "g int 0\n" - ] - } - ], + "outputs": [], "source": [ "a = 7.54\n", "str(a)\n", @@ -961,7 +973,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-18152675870ab0f1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "That is actually what happens when you use the `print()` commands with a numeric value.\n", "\n", @@ -970,69 +991,45 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "data": { - "text/plain": [ - "5.74" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "float('5.74')" ] }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "774" - ] - }, - "execution_count": 34, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "int('774')" ] }, { "cell_type": "code", - "execution_count": 35, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(5+3j)" - ] - }, - "execution_count": 35, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "complex('5+3j')" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ce079e7a0832a0f5", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.7** Define a list of parameters with as many types as possible, i.e. all the examples you see above and maybe a few more. Use `%whos` to see how they look inside the computers' memory. Try to change their format and rerun the `%whos` command." ] @@ -1041,7 +1038,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, "scrolled": true }, "outputs": [], @@ -1056,7 +1052,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d453c295a6954f9d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Python can do math\n", "\n", @@ -1065,20 +1070,9 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "1+1" ] @@ -1092,17 +1086,9 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "6\n" - ] - } - ], + "outputs": [], "source": [ "a = 5\n", "print(a+1)" @@ -1110,166 +1096,113 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-74f5aea3a72d2f71", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.8** Discover what the following Python operators do by performing some math with them: `*`, `-`, `/`, `**`, `//`, `%`. Print the value of the mathematical operation to the command line in susequent cells." ] }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "data": { - "text/plain": [ - "8" - ] - }, - "execution_count": 38, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Try out *\n", - "4*2" + "\n", + "# What did it do? Add your answer here:" ] }, { "cell_type": "code", - "execution_count": 39, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Try out -\n", - "4-2" + "\n", + "# What did it do? Add your answer here:" ] }, { "cell_type": "code", - "execution_count": 40, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2.0" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Try out /\n", - "4/2" + "\n", + "# What did it do? Add your answer here:" ] }, { "cell_type": "code", - "execution_count": 41, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "16" - ] - }, - "execution_count": 41, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Try out **\n", - "4**2\n" + "\n", + "# What did it do? Add your answer here:" ] }, { "cell_type": "code", - "execution_count": 42, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "data": { - "text/plain": [ - "2" - ] - }, - "execution_count": 42, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Try out //\n", - "4//2" + "\n", + "# What did it do? Add your answer here:" ] }, { "cell_type": "code", - "execution_count": 43, + "execution_count": null, "metadata": { "scrolled": false }, - "outputs": [ - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 43, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Try out %\n", - "4%2" + "\n", + "# What did it do? Add your answer here:" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8cd6e8f83013a575", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Another handy built-in function is `abs()`:" ] }, { "cell_type": "code", - "execution_count": 44, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "10\n", - "10\n", - "1.0\n", - "1.4142135623730951\n" - ] - } - ], + "outputs": [], "source": [ "print(abs(10))\n", "print(abs(-10))\n", @@ -1279,7 +1212,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ec6f5beb19edae73", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can find the full list of built-in math commands on the python documentation webpage:\n", "\n", @@ -1288,7 +1230,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9873288c26d190e4", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Sending data to python using input()\n", "\n", @@ -1301,20 +1252,9 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "45\n", - "\n", - "The input was:\n", - "45\n" - ] - } - ], + "outputs": [], "source": [ "a = input()\n", "\n", @@ -1325,54 +1265,50 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-84fd218d798174b7", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Even if we type a number into the input box, it will always return a string variable of type `str`:" ] }, { "cell_type": "code", - "execution_count": 46, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "str" - ] - }, - "execution_count": 46, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "type(a)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8f2b377eabcbec2b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "If we want to use our input as a number, we have to convert it to a number, for example, by using the `float()` function:" ] }, { "cell_type": "code", - "execution_count": 47, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "45\n", - "\n", - "The value of a is: 45.0\n", - "a has the type: <class 'float'>\n" - ] - } - ], + "outputs": [], "source": [ "a = input()\n", "a = float(a)\n", @@ -1382,66 +1318,75 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-aa5bc8c7aabb059d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can also specify text for the label of the input box: " ] }, { "cell_type": "code", - "execution_count": 48, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Enter a number: 23\n" - ] - } - ], + "outputs": [], "source": [ "a = input(\"Enter a number: \")" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-39dc108b097dadf4", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.9** Use the `input` function to get parameters of integer, float and string format into the computer." ] }, { "cell_type": "code", - "execution_count": 51, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "type een woord45\n", - "45 <class 'str'>\n", - "type een integer45\n", - "45 <class 'int'>\n" - ] - } - ], + "outputs": [], "source": [ - "# Your code here\n", + "### BEGIN SOLUTION\n", "a = input(\"type een woord\")\n", "a = str(a)\n", "print(a, type(a))\n", "b = input(\"type een integer\")\n", "b = int(b)\n", - "print(b, type(b))" + "print(b, type(b))\n", + "### END SOLUTION" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-53972ca5ec4efa32", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Tab completion in Jupyter Notebooks\n", "\n", @@ -1457,10 +1402,8 @@ }, { "cell_type": "code", - "execution_count": 52, - "metadata": { - "collapsed": true - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ "this_is_my_very_long_variable_name = 5\n", @@ -1469,70 +1412,84 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-df0c3eda1082e7ed", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Now click on the following code cell, go the end of the lines in this cell and try pushing `Tab`:" ] }, { "cell_type": "code", - "execution_count": 53, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "6" - ] - }, - "execution_count": 53, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "this_is_another_ones\n" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-26572cb3a032030b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Handy! Jupyter did the typing for me! \n", "\n", - "If multiple things match, you will get a drop-down box and can select the one you want. So press \\texttt{Tab} : after" + "If multiple things match, you will get a drop-down box and can select the one you want. So press `Tab` : after" ] }, { "cell_type": "code", - "execution_count": 54, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "5" - ] - }, - "execution_count": 54, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "this_is_my_very_long_variable_name" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-cc31b788b063face", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can also keep on typing: if you just type `a` after you hit tab and then hit tab again, it will finish the typing for you." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6ea392babfac0516", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.10** Use tab completion on the initial letters of a few of the commands that have been presented. Along the way you will discover many more Python commands!" ] @@ -1541,7 +1498,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, "scrolled": true }, "outputs": [], @@ -1551,7 +1507,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f4fabb371d6b9224", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Understanding Python Errors\n", "\n", @@ -1562,23 +1527,11 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'printt' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m<ipython-input-55-06c3cfa8cc1e>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0ma\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m5\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mprintt\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0ma\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;31mNameError\u001b[0m: name 'printt' is not defined" - ] - } - ], + "outputs": [], "source": [ "a = 5\n", "printt(a)" @@ -1586,13 +1539,22 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4d9a7d42147dd830", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "After your code cell, you will see some colored text called a \"Traceback\". This \"Traceback\" is the way that python tries to tell you where the error is. \n", "\n", "Let's take a look at the traceback:\n", "\n", - "<img src=\"anatomy_of_an_error.png\" width=80%></img>\n", + "<img src=\"resource/asnlib/public/Notebook_1_anatomy_of_an_error.png\" width=60%></img>\n", "\n", "The traceback contains three important details that can help you:\n", "\n", @@ -1614,83 +1576,77 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'non_existant_variable' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m<ipython-input-56-868ad4237246>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mnon_existant_variable\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;31mNameError\u001b[0m: name 'non_existant_variable' is not defined" - ] - } - ], + "outputs": [], "source": [ "print(non_existant_variable)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5deb43b858deacc0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Another common type of error is a `SyntaxError`, which means you have typed something that python does not understand:" ] }, { "cell_type": "code", - "execution_count": 57, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "ename": "SyntaxError", - "evalue": "invalid syntax (<ipython-input-57-14939c57c979>, line 1)", - "output_type": "error", - "traceback": [ - "\u001b[0;36m File \u001b[0;32m\"<ipython-input-57-14939c57c979>\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m a = a $ 5\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" - ] - } - ], + "outputs": [], "source": [ "a = a $ 5" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b33dea484458dc18", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can also get errors if you try to use operators that do not work with the data type you have. For example, if you try to \"divide\" two strings:" ] }, { "cell_type": "code", - "execution_count": 58, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "unsupported operand type(s) for /: 'str' and 'str'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m<ipython-input-58-c541b10dc073>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[1;34m\"You cannot \"\u001b[0m \u001b[1;33m/\u001b[0m \u001b[1;34m\"divide strings\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m: unsupported operand type(s) for /: 'str' and 'str'" - ] - } - ], + "outputs": [], "source": [ "\"You cannot \" / \"divide strings\"" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b40abf4de47d6118", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Here, you get a `TypeError`: the division operator is a perfectly fine syntax, it just does not work with strings. \n", "\n", @@ -1706,30 +1662,27 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-737d6eb17c2a2f4f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.11** Run the following code and try to understand what is going wrong by reading the error message." ] }, { "cell_type": "code", - "execution_count": 59, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "ename": "ZeroDivisionError", - "evalue": "division by zero", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m<ipython-input-59-ab30c62b603d>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0ma\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m10\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mb\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mc\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0ma\u001b[0m\u001b[1;33m/\u001b[0m\u001b[0mb\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;31mZeroDivisionError\u001b[0m: division by zero" - ] - } - ], + "outputs": [], "source": [ "a=10\n", "b=0\n", @@ -1738,57 +1691,28 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'practicum' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m<ipython-input-60-91753ee1b689>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[1;36m4\u001b[0m \u001b[1;33m+\u001b[0m \u001b[0mpracticum\u001b[0m\u001b[1;33m*\u001b[0m\u001b[1;36m3\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;31mNameError\u001b[0m: name 'practicum' is not defined" - ] - } - ], + "outputs": [], "source": [ "4 + practicum*3" ] }, { "cell_type": "code", - "execution_count": 61, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "must be str, not int", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m<ipython-input-61-074e46b8f941>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0md\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m'practicum is great'\u001b[0m \u001b[1;33m+\u001b[0m \u001b[1;36m2\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m: must be str, not int" - ] - } - ], + "outputs": [], "source": [ "d='practicum is great' + 2" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] } ], "metadata": { + "celltoolbar": "Create Assignment", + "jupytext": { + "formats": "ipynb,md" + }, "kernelspec": { "display_name": "Python 3", "language": "python", @@ -1804,7 +1728,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.5" }, "toc": { "base_numbering": 1, @@ -1822,7 +1746,7 @@ "width": "402.797px" }, "toc_section_display": true, - "toc_window_display": true + "toc_window_display": false } }, "nbformat": 4, diff --git a/Notebook 1/Notebook 1 Python Basics.md b/Notebook 1/Notebook 1 Python Basics.md index aeb0114f1f4609e15667315b1204f8eea899badc..3046d18632c91e2865e95b576038151a43da9d89 100644 --- a/Notebook 1/Notebook 1 Python Basics.md +++ b/Notebook 1/Notebook 1 Python Basics.md @@ -13,7 +13,8 @@ jupyter: name: python3 --- -# Python Basics +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-e68e92ac1082dd04"} --> +# Python Basics In this course, you will work using a platform called "Jupyter Notebooks". Jupyter notebooks are a way to combine formatted text (like the text you are reading now), Python code (which you will use below), and the result of your code and calculations all in one place. Go through these notebooks and run the examples. Try to understand their functioning and, if necessary, add code (such as print statements or variable overviews) to make it clear for you. In addition, there are exercises and practice cells where you can program for yourself. Don't be afraid to start coding yourself, writing code and making mistakes is the best way to learn Python. @@ -36,16 +37,18 @@ Python is an (<a href=https://en.wikipedia.org/wiki/Interpreted_language>interpr Every time you run python, either by running it on the command line, or by running it in a Jupyter notebook like you are now, a python **"kernel"** is created. This kernel is a copy of the python program ("interpreter") that runs continuously on your computer (until you stop it). -Jupyter notebooks are a way to interact with the python kernel. Notebooks are divided up into "cells", which can be either a text (<a href=https://en.wikipedia.org/wiki/Markdown>markdown</a> format) cell, like this one, or a code cell (containing your code), like the cell below it. +Jupyter notebooks are a way to interact with the python kernel. Notebooks are divided up into "cells", which can be either a text cell (in a special text formatting language called <a href=https://en.wikipedia.org/wiki/Markdown>markdown</a>), like the one you are reading now. There are also code cell (containing your code), like the cell below it. The selected cell is surrounded by a box. If you type "enter" in a text cell you can start editing the cell. If you push "Run" above, or type "Shift-Enter", the code will be "run". If it is a code cell, it will run a command (see below). If it is a markdown cell, it will "compile" the markdown text language into formatted (HTML) text. You can give commands to this kernel by typing commands using the python language into the code cells of the notebook. Here, you can find an example of a code cell that contains a simple python command `print`, which prints a text string to the command line. +<!-- #endregion --> ```python print("Hello world") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ee6ac0827c6a6783"} --> To send this command to the python kernel, there are several options. First, select the cell (so that it is either blue or green), and then: 1. Click on the **Run** button above in the toolbar. This will execute the cell and move you to the next cell. @@ -54,11 +57,12 @@ To send this command to the python kernel, there are several options. First, sel When you run the cell, the code will be sent to the python kernel, which will translate your python command into a binary language your computer CPU understands, send it to the CPU, and read back the answer. If the code you run produces an "output", meaning that the kernel will send something back to you, then the output that your code produces will be displayed below the code cell in the "output" section of the code cell. This is a scheme of what this process looks like "behind the scenes": -<img width=60% src="behind_the_scenes.png"></img> +<img width=60% src="resource/asnlib/public/Notebook_1_behind_the_scenes.png"></img> After you have run the code cell, a number will appear beside your code cell. This number tell you in which order that piece of code was sent to the kernel. Because the kernel has a "memory", as you will see in the next section, this number can be useful so that you remember in which order the code cells in your notebook were executed. In the example above, the code cell contained only a single line of code, but if you want, you can include as many lines as you want in your code cell: +<!-- #endregion --> ```python print("Hello") @@ -66,60 +70,76 @@ print("world") print("Goodbye") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8698bcb51f5ce066"} --> In the above, the text in the code cell are all python commands. In addition, if you start a line in a code cell with a `#`, python will ignore this line of text. This is use to add **comments** to your code. It is good programming practice to use comments to explain what the code is doing: +<!-- #endregion --> ```python # This will print out a message print("This is a message") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4919a733b4210f11"} --> **Exercise 1.1** Print your own string to the command line. Can you use special characters as well? +<!-- #endregion --> ```python -# Your code here +### BEGIN SOLUTION +print(" df# ") +### END SOLUTION ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-74c3c19c58006da4"} --> ## The Python kernel has a memory In addition to asking python to do things for you, like the "Hello world" example above, you can also have python remember things for you. To do this, you can use the following syntax: +<!-- #endregion --> ```python a = 5 ``` -In python, the `=` symbol represents the **assignment operator**: it is an instruction to **assign** the value of `5` to the variable `a`. If variable `a` already exists, it will be over-written with the new value. If variable `a` does not yet exist, then python will create a new variable for you automatically. +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-84c1e3509c93658b"} --> +In python, the `=` symbol represents the **assignment operator**: it is an instruction to **assign** the value of `5` to the variable `a`. If variable `a` already exists, it will be over-written with the new value (in fact, `a` is a python object, something that we will explain in the optional notebook in more detail). If variable `a` does not yet exist, then python will create a new variable for you automatically. For you, the cell above will create a "variable" named `a` in memory of the python kernel that has the value of 5. We can check this by printing the value of a: +<!-- #endregion --> ```python print(a) ``` -Note that we can also add a message to this by combining this with a message with the `print()` statement by combining things with commas: +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-99b80d998fcc74ff"} --> +Besides numerical values variables can also be strings, which are sequences of characters. You make a string by putting the text between quotes. + +Note that we can also add a message if we add a string and a numerical value in the `print()` statement by combining things with commas: +<!-- #endregion --> ```python print("The value of a is",a) ``` -**Exercise 1.2** Combine a string, a variable, and a numerical values in a single `print` statement using the `,` separator. +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a4382a92b6e9aeec"} --> +**Exercise 1.2** Combine multiple strings and numerical values in a single `print` statement using the `,` separator. +<!-- #endregion --> ```python -# Your code here +# your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-cbf03d005dae5d5d"} --> **Exercise 1.3** Change the value of `a` to 7 by executing the following cell, and then re-run the **above** cell containing the command `print(a)` (the one with output `5`). What value gets printed now in that cell? +<!-- #endregion --> ```python -a = +# your code here to assign the value 7 to variable a ``` -As you can see in notebooks that the location of your code doesn’t matter, but the order in which you execute them does!! - -*(Note that this is very different from code that you would write in a python script and run with a more conventional normal python interpreter, like on a command line or in the matlab-like Spyder interface. In this case, code is always executed top to bottom. For those of you familiar with conventional computer programming, notebooks might seem confusing at first. You can think of it though as having a bunch of different chunks of code you can send to the interpreter in any order you want.)* - -Although the code does not need to be executed from top to bottom in a notebook, it is important to remember that if you send somebody else your notebook, they will probably try to execute the cells in order. Therefore, when you (think) you are done making a notebook, you should **ALWAYS** restart the kernel and run the cells from top to bottom just to make sure it works. +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2b713ce46a7bca48"} --> +As you can see in notebooks that the location of your code doesn’t matter, but the order in which you execute them does!! We can also use variables to set the values of other variables: +<!-- #endregion --> ```python b = 0 @@ -128,16 +148,20 @@ b = a print(b) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-16240c968680ffa4"} --> Sometimes, if you execute a lot of cells, or maybe even re-execute a cell after changing its contents, you might lose track of what variables are defined in the memory of your python kernel. For this, there is a convenient built-in "magic" command called `%whos` that can list for you all the variables that have been defined in your kernel, along with their values: +<!-- #endregion --> ```python a=5 %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d61c3a57fbd93ed6"} --> _(Some notes about `%whos`: `%whos` is not a "native" command of the python language, but instead a "built-in" command that has been added by the creators of Jupyter. Because of this, you cannot use it outside of Jupyter / iPython...)_ If we define some new variables, they will also appear in the list of defined variables if you execute `%whos`: +<!-- #endregion --> ```python c = 10 @@ -148,9 +172,11 @@ d = 15.5 %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-bea496517f99df02"} --> In this case the variable named is displayed, its value, but also its type. Type defines the format in which a variable is stored in memory. In this case `int` stands for integer and `float` stands for floating point number, which is the usual way in which real numbers are stored in a computer. We will learn more about Python variable types below. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-80d1461986f4d365"} --> ## Starting and stopping the kernel When you open a notebook for the first time, a new kernel will be started for you, which will have nothing in your memory. @@ -170,44 +196,46 @@ For this, there is both a menubar "Kernel" at the top, along with two useful but * "Stop": tells the kernel to abort trying to run the code it is working on, but does not erase its memory * "Restart": "kill" the kernel (erasing its memory), and start a new one attached to the notebook. +<img src="resource/asnlib/public/Notebook_1_stop_button.png" width=20%></img> +<img src="resource/asnlib/public/Notebook_1_restartkernelmenu.png" width=60%></img> + To see this in action, you can execute the following cell, which will do nothing other than wait for 10 minutes: +<!-- #endregion --> ```python from time import sleep sleep(10*60) ``` -You will notice that while a cell is running, the text beside it shows `In [*]:`. The `*` indicates that the cell is being executed, and will change to a number when the cell is finished. You will also see that the small circle beside the `Python 3` text on the right side of the Jupyter menu bar at the top of the page will become solid. Unless you have a lot of patience, you should probably stop the kernel, using the "Stop" button: - -<img src="stop_button.png"></img> - -or the menu item "Kernel / Interrupt". - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6c0352859b13ed82"} --> +You will notice that while a cell is running, the text beside it shows `In [*]:`. The `*` indicates that the cell is being executed, and will change to a number when the cell is finished. You will also see that the small circle beside the `Python 3` text on the right side of the Jupyter menu bar at the top of the page will become solid. Unless you have a lot of patience, you should probably stop the kernel, using the "Stop" button, or the menu item "Kernel / Interrupt". **Exercise 1.4** List the stored variables using the `%whos` command. Subsequently, restart the kernel. What variables are stored in the memory of the kernel before and after the restart? +<!-- #endregion --> ```python -# Your code here +# add your code to exectue the command %whos here, then restart the kernel and run this cell again ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-fd74d0d65cf02f0a"} --> ## Python variable types As we saw above, in Python, variable have a property that is called their "type". When you use the assignment operator `=` to assign a value to a variable, python will automatically pick a variable type it thinks fits best, even changing the type of an existing variable if it thinks it is a good idea. You have, in fact, already seen information about the types of variables in the `%whos` command again: +<!-- #endregion --> ```python -a = 1 -b = 2 -c = 3.3 %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6f2bfadc72282277"} --> In the second column, you can see the **type** that python chose for the variables we created. `int` corresponds to integer numbers, `float` corresponds to floating-point numbers. You can see that for variable `c`, python had to choose a `float` type (because 15.5 is not an integer), but for `a` and `b`, it chose integer types. _(In general, Python tries to choose a variable type that makes calculations the fastest and uses as little memory as possible.)_ If you assign a new value to a variable, it can change the variables type: +<!-- #endregion --> ```python a = a/2 @@ -217,21 +245,21 @@ a = a/2 %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8ac075dfca80d989"} --> Because 5/2 = 2.5, Python decided to change the type of variable `a` from `int` to `float` after the assignment operation `a = a/2`. When you are using floating point numbers, you can also use an "exponential" notation to specify very big or very small numbers: +<!-- #endregion --> ```python c = 1.5e-8 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-9f4aa0b51687698a"} --> The notation `1.5e-8` is a notation used in python to indicate the number $1.5 \times 10^{-8}$. -A third type of mathematical variable type that you may use in physics is a complex number: - -https://en.wikipedia.org/wiki/Complex_number - -In python, you can indicate a complex number by using `1j`, which is the python notation for the complex number $i$: +A third type of mathematical variable type that you may use in physics is a complex number. In python, you can indicate a complex number by using `1j`, which is the python notation for the complex number $i$: +<!-- #endregion --> ```python d = 1+1j @@ -241,13 +269,17 @@ d = 1+1j %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-212eb128ab19078d"} --> The notation `1j` is special, in particular because there is **no space** between the number `1` and the `j`. This is how Python knows that you are telling it to make a complex number (and not just referring to a variable named `j`...). The number in front of the `j` can be any floating point number: for example, +<!-- #endregion --> ```python -0.5*d +0.5j ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-770f5a2d267d39b4"} --> In addition to the mathematical variable types listed above, there are also other types of variables in Python. A common one you may encounter is the "string" variable type `str`, which is used for pieces of text. To tell Python you want to make a string, you enclose the text of your string in either single forward quotes `'` or double forward quotes `"`: +<!-- #endregion --> ```python e = "This is a string" @@ -256,9 +288,13 @@ f = 'This is also a string' ```python %whos +print(e) +print(f) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6e76757478040b18"} --> You can also make multiline strings using three single quotes: +<!-- #endregion --> ```python multi = \ @@ -270,28 +306,35 @@ multiple lines. print(multi) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-31e4b55de81cef07"} --> Note here that I have used a backslash: this a way to split Python code across multiple lines. Although it's not obvious, Python can also do "operations" on strings, the `+` mathematical opeartors we saw above also works with strings. - -**Exercise 1.5** Discover what the `+` operator does to a string, i.e. print the output of the sum of two strings. What about `*`? +**Exercise 1.5** Discover what the `+` operator does to a string, i.e. print the output of the sum of two strings. +<!-- #endregion --> ```python # Your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1fa7be3de8eb7f5a"} --> There is one more useful variable type we will introduce here: the "boolean" type `bool`. Boolean variable can have two values: `True` and `False`. You type them in directly as `True` and `False` with no quotes (you will see them turn green). +<!-- #endregion --> ```python -g = True +g = 0 ``` ```python %whos ``` -We will use boolean types much more later when we look at program control flow, but a simple example is the `if` statement: +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-12ff71b21d9d01e3"} --> +We will use boolean types much more later when we look at program control flow, but a simple example using the `if` statement is given below: + +No panic if you don't yet understand the if statement, there will be another entire notebook dedicated to them. This is just an example of why boolean variables exist. +<!-- #endregion --> ```python if True: @@ -304,17 +347,21 @@ if not g: print("g is not true!") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7f2acb9e44b581d8"} --> You can try changing the value of `g` above to `False` and see what happens if you run the above code cell again. Also, useful to know: numbers (both `int` and `float`) can also be used in True / False statements! Python will interpret any number that is not zero as `True` and any number that is zero as `False`. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4016c53a455f3656"} --> **Exercise 1.6** Discover which numbers can be used as `True` and `False` in Python by changing the value of `g` above and re-running the cells. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c1323d47dae023f1"} --> ## Converting variables between different types We can also convert a value from one type to another by using functions with the same name as the type that we want to convert them to. Some examples: +<!-- #endregion --> ```python float(5) @@ -324,33 +371,47 @@ float(5) int(7.63) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-e7f8cf2c018ef4fd"} --> Note that when converting an `float` to an `int`, python does not round off the value, but instead drops all the numbers off after the decimal point (it "trucates" it). If we want to convert to an integer and round it off, we can use the `round()` function: +<!-- #endregion --> ```python -round(7.63) +b = round(7.63) +print(b) ``` -Round also converts the data type to an `int`. - -The idea of converting one variable type to an another using functions based on the variable type you want to convert to works for many variable types. All variable types in python "provide" such a function. +```python +type(b) +print(b+0.4) +``` -Sometimes, you have will lose information in this process: for example, converting a `float` to an `int`, we lose all the numbers after the decimal point. In this example, Python makes a guess at what you probably want to do, and decides to round off the floating point number to the nearest integer. +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f3f5149b2d627e26"} --> +This works for conversions between many types. Sometimes, you have will lose information in this process: for example, converting a `float` to an `int`, we lose all the numbers after the decimal point. In this example, Python makes a guess at what you probably want to do, and decides to round off the floating point number to the nearest integer. Sometimes, Python can't decide what to do, and so it triggers an error: +<!-- #endregion --> ```python float(1+1j) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-582c3f589fac8746"} --> A very useful feature is that Python can convert numbers into strings: +<!-- #endregion --> ```python -str(7.54) +a = 7.54 +str(a) +b = a + 1 +print(b) +%whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-18152675870ab0f1"} --> That is actually what happens when you use the `print()` commands with a numeric value. But also very useful is that as long as your string is easily convertable to a number, python can do this for you too! +<!-- #endregion --> ```python float('5.74') @@ -364,7 +425,9 @@ int('774') complex('5+3j') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ce079e7a0832a0f5"} --> **Exercise 1.7** Define a list of parameters with as many types as possible, i.e. all the examples you see above and maybe a few more. Use `%whos` to see how they look inside the computers' memory. Try to change their format and rerun the `%whos` command. +<!-- #endregion --> ```python # Your parameters list @@ -375,9 +438,11 @@ b= %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d453c295a6954f9d"} --> ## Python can do math Python has a set of math functions that are directly built in to the language. You can use python as a calculator! +<!-- #endregion --> ```python 1+1 @@ -390,39 +455,49 @@ a = 5 print(a+1) ``` -**Exercise 1.8** Discover what the following Python operators do by performing some math with them: `*`, `-`, `/`, `**`, `//`, `%`. Add your own code to the following cells to "discover" what the operators do. You may need to try them out for a couple of different cases printing out the result. +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-74f5aea3a72d2f71"} --> +**Exercise 1.8** Discover what the following Python operators do by performing some math with them: `*`, `-`, `/`, `**`, `//`, `%`. Print the value of the mathematical operation to the command line in susequent cells. +<!-- #endregion --> ```python -# What does * do? -your code +# Try out * + +# What did it do? Add your answer here: ``` ```python -# What does - do? -your code +# Try out - + +# What did it do? Add your answer here: ``` ```python -# What does / do? -your code +# Try out / + +# What did it do? Add your answer here: ``` ```python -# What does ** do? -your code +# Try out ** + +# What did it do? Add your answer here: ``` ```python -# What does // do? -your code +# Try out // + +# What did it do? Add your answer here: ``` ```python -# What does % do? -your code +# Try out % + +# What did it do? Add your answer here: ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8cd6e8f83013a575"} --> Another handy built-in function is `abs()`: +<!-- #endregion --> ```python print(abs(10)) @@ -431,11 +506,13 @@ print(abs(1j)) print(abs(1+1j)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ec6f5beb19edae73"} --> You can find the full list of built-in math commands on the python documentation webpage: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-9873288c26d190e4"} --> ## Sending data to python using input() So far, we have seen examples of "output": Python telling us stuff, like in the first "Hello world" example above. @@ -443,6 +520,7 @@ So far, we have seen examples of "output": Python telling us stuff, like in the And we have seen example of "code": us giving instructions to python to do things. In addition, we can also send stuff to Python. Often in physics, we do this by having Python read data files, which we will cover later, but we can also send information to python using the `input()` command: +<!-- #endregion --> ```python a = input() @@ -452,13 +530,17 @@ print("The input was:") print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-84fd218d798174b7"} --> Even if we type a number into the input box, it will always return a string variable of type `str`: +<!-- #endregion --> ```python type(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8f2b377eabcbec2b"} --> If we want to use our input as a number, we have to convert it to a number, for example, by using the `float()` function: +<!-- #endregion --> ```python a = input() @@ -467,25 +549,36 @@ print("\nThe value of a is:", a) print("a has the type:", type(a)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-aa5bc8c7aabb059d"} --> You can also specify text for the label of the input box: +<!-- #endregion --> ```python a = input("Enter a number: ") ``` -**Exercise 1.9** Use the `input` function to create variables in the kernel of the following types: an `integer`, a `float`, and a `string`. +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-39dc108b097dadf4"} --> +**Exercise 1.9** Use the `input` function to get parameters of integer, float and string format into the computer. +<!-- #endregion --> ```python -# Your code here +### BEGIN SOLUTION +a = input("type een woord") +a = str(a) +print(a, type(a)) +b = input("type een integer") +b = int(b) +print(b, type(b)) +### END SOLUTION ``` -<!-- #region --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-53972ca5ec4efa32"} --> ## Tab completion in Jupyter Notebooks -Computer programmers often forget things. They even sometimes forget the names of the variables they have created themselves! Fruthermore, computer programmers always like to save typing if they can. +Computer programmers often forget things, and often they what variables they have defined. Also, computer programmers always like to save typing if they can. -For this reason, the people who made Jupyter notebooks included a handy feature called "Tab completion" (which is actually something that has been around for <a href=https://en.wikipedia.org/wiki/Command-line_completion>a long time</a> in unix command line environments). +For this reason, the people who made Jupyter notebooks included a handy feature called "Tab completion" (which is actually something that has been around for <a href=https://en.wikipedia.org/wiki/Command-line_completion>a long time</a> in unix and ms-dos command line environments). The idea is that if you start typing part of the name of a variable or part of the name of a function, and then push the `Tab` key, Jupyter will bring up a list of the variable and function names that match what you have started to type. If ony one matches, it will automatically type the rest for you. If multiple things match, it will offer you a list: you can either keep typing until it's unique and type tab again, or you can use the cursor keys to select the one you want. @@ -497,48 +590,56 @@ this_is_my_very_long_variable_name = 5 this_is_another_ones = 6 ``` -Now click on the following code cell, go the end of the line in this cell and try pushing `Tab`: +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-df0c3eda1082e7ed"} --> +Now click on the following code cell, go the end of the lines in this cell and try pushing `Tab`: +<!-- #endregion --> ```python -this +this_is_another_ones + ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-26572cb3a032030b"} --> Handy! Jupyter did the typing for me! -If multiple things match, you will get a drop-down box and can select the one you want. If you want, you can then use the arrow keys to pick one of them and type `Enter` to finish the tab completion. Try it here: +If multiple things match, you will get a drop-down box and can select the one you want. So press `Tab` : after +<!-- #endregion --> ```python -th +this_is_my_very_long_variable_name ``` -You can also keep on typing: if you just type `a` after you hit tab and then hit tab again, it will finish the typing for you. Try it here: - -```python -th -``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-cc31b788b063face"} --> +You can also keep on typing: if you just type `a` after you hit tab and then hit tab again, it will finish the typing for you. +<!-- #endregion --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6ea392babfac0516"} --> **Exercise 1.10** Use tab completion on the initial letters of a few of the commands that have been presented. Along the way you will discover many more Python commands! +<!-- #endregion --> ```python # Your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f4fabb371d6b9224"} --> ## Understanding Python Errors Sometimes, the code you type into a code cell will not work. In this case, Python will not execute your code, but instead print out an error message. In this section, we will take a look at these error messages and learn how to understand them. Let's write some code that will give an error. For example, this is a typo in the name of the `print()` command: +<!-- #endregion --> ```python a = 5 printt(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4d9a7d42147dd830"} --> After your code cell, you will see some colored text called a "Traceback". This "Traceback" is the way that python tries to tell you where the error is. Let's take a look at the traceback: -<img src="anatomy_of_an_error.png" width=80%></img> +<img src="resource/asnlib/public/Notebook_1_anatomy_of_an_error.png" width=60%></img> The traceback contains three important details that can help you: @@ -556,24 +657,29 @@ A `NameError` means that python tried to find a function or variable that you ha At the very end of the traceback, Python tries to explain what the problem was: in this case, it is telling you that there is no function named `printt`. You will also get a `NameError` if you try to use a variable that doesn't exist: +<!-- #endregion --> ```python print(non_existant_variable) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5deb43b858deacc0"} --> Another common type of error is a `SyntaxError`, which means you have typed something that python does not understand: +<!-- #endregion --> ```python a = a $ 5 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b33dea484458dc18"} --> You can also get errors if you try to use operators that do not work with the data type you have. For example, if you try to "divide" two strings: +<!-- #endregion --> ```python "You cannot " / "divide strings" ``` -<!-- #region --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b40abf4de47d6118"} --> Here, you get a `TypeError`: the division operator is a perfectly fine syntax, it just does not work with strings. @@ -586,7 +692,9 @@ Sometimes, you can learn more about what the error means by reading these docume In last resort, you can also always try a internet search: googling the error message can help, and there are also lots of useful posts on <a href=https://stackexchange.com>stack exchange</a> (which you will also often find by google). <!-- #endregion --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-737d6eb17c2a2f4f"} --> **Exercise 1.11** Run the following code and try to understand what is going wrong by reading the error message. +<!-- #endregion --> ```python a=10 @@ -601,129 +709,3 @@ c=(a/b) ```python d='practicum is great' + 2 ``` - -## Solutions to Exercises - - -**Exercise 1.1** - -```python -print("Notebooks are fun!") -``` - -**Exercise 1.2** - -```python -a=4 -print("The value of a is",a,"and not",5) -``` - -**Exercise 1.3** - -```python -a = 7 -# the new value gets printed is the answer -``` - -**Exercise 1.4** - -```python -%whos -# After restarting, there are no variables left in the memory of the kernel -``` - -**Exercise 1.5** - -```python -# Your code here -print("The plus operator " + "concatenates strings") -print("Blah "*5) -``` - -**Exercise 1.6** - -```python -# zero is false, all other numbers are true (see next notebook too) -``` - -**Exercise 1.7** - -```python -# try it out just... -``` - -**Exercise 1.8** - -```python -# What does * do? -6*6 -``` - -```python -# What does - do? -6-3 -``` - -```python -# What does / do? -7/2 -``` - -```python -# What does ** do? -2**3 -``` - -```python -# What does // do? -print(6//3) -print(7//3) -print(8//3) -print(9//3) -``` - -```python -# What does % do? -print(6%3) -print(7%3) -print(8%3) -print(9%3) -``` - -**Exercise 1.9** - -```python -a=int(input("An int please:")) -b=float(input("A float please:")) -c=input("A string please:") -``` - -**Exercise 1.10** - -```python -# Check the help on these, they are pretty cool, particularly the last :) -ret -oc -eva -has -exe -``` - -**Exercise 1.11** Run the following code and try to understand what is going wrong by reading the error message. - -```python -a=10 -b=0 -c=(a/b) -# should be obvious -``` - -```python -4 + practicum*3 -# depends on what variables you've defined before... -``` - -```python -d='practicum is great' + 2 -# you can't add numbers and strings... -``` diff --git a/Notebook 1/resource/.gitkeep b/Notebook 1/resource/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Notebook 1/resource/asnlib/.gitkeep b/Notebook 1/resource/asnlib/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Notebook 1/resource/asnlib/public/.gitkeep b/Notebook 1/resource/asnlib/public/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Notebook 1/anatomy_of_an_error.png b/Notebook 1/resource/asnlib/public/Notebook_1_anatomy_of_an_error.png similarity index 100% rename from Notebook 1/anatomy_of_an_error.png rename to Notebook 1/resource/asnlib/public/Notebook_1_anatomy_of_an_error.png diff --git a/Notebook 1/behind_the_scenes.png b/Notebook 1/resource/asnlib/public/Notebook_1_behind_the_scenes.png similarity index 100% rename from Notebook 1/behind_the_scenes.png rename to Notebook 1/resource/asnlib/public/Notebook_1_behind_the_scenes.png diff --git a/Notebook 1/resource/asnlib/public/Notebook_1_restartkernelmenu.png b/Notebook 1/resource/asnlib/public/Notebook_1_restartkernelmenu.png new file mode 100644 index 0000000000000000000000000000000000000000..afcdd61223dd640c8a91c943ac0da2180381818b Binary files /dev/null and b/Notebook 1/resource/asnlib/public/Notebook_1_restartkernelmenu.png differ diff --git a/Notebook 1/stop_button.png b/Notebook 1/resource/asnlib/public/Notebook_1_stop_button.png similarity index 100% rename from Notebook 1/stop_button.png rename to Notebook 1/resource/asnlib/public/Notebook_1_stop_button.png diff --git a/Notebook 2/Notebook 2 Functions.ipynb b/Notebook 2/Notebook 2 Functions.ipynb index c643628fdce5edb49bbf3636b607beca0f1588f8..63aaf5e8485d487721ba02c0a4d7df9791c53120 100644 --- a/Notebook 2/Notebook 2 Functions.ipynb +++ b/Notebook 2/Notebook 2 Functions.ipynb @@ -2,7 +2,16 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-98b92489332760b6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "# Functions in Python\n", "\n", @@ -12,10 +21,12 @@ "\n", "* Student is able to define functions with input parameters to execute a piece of code (use functions as \"methods\")\n", "* Student is able to create and use functions that return a value (or multiple values)\n", - "* Student is able to import functions from libraries / modules\n", + "* Student is able to import and use functions from libraries / modules\n", "* Student is able to use Shift-Tab to bring up the help for a function from a library\n", "* Student is able to predict if a variable name in a function refers to a local variable or a global variable\n", "\n", + "From here, the exercises get a bit more complicated. The solutions for the exercises in this notebook, and future ones, can be found at the bottom of the notebook, so that you can check your answer or have a peek if you are getting stuck.\n", + "\n", "## Functions to save typing\n", "\n", "In programming, you often want to repeat the same sequence of commands over and over again. \n", @@ -63,7 +74,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-fb10310a1ec1a391", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "To save a lot of typing, one can define a simple function to do this work for us. To define a function, you use the following syntax:\n", "\n", @@ -95,7 +115,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c16ac57175c4a00e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Once you have defined your function, you can execute it by using the code `function_name()`. \n", "\n", @@ -137,14 +166,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a12ce8709fcf3b9f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In this example, it may not be such a big deal, but you can imagine that as the code in your function becomes more and more complicated, it will save you a lot of time. Also, imagine that I wanted to change the wording of the sentence I print: in the case with the function, I would only have to do this once, while in the example without function, I would have to manually change this at 5 different places. " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-cc004d60a3c3b17c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.1** Write your own function that contains two lines of code. The first line should make a new variable `var2` that is `var` converted to an integer. The second line of your code should print the value of `var2`. \n", "\n", @@ -166,7 +213,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1be65f1712abb44d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Functions with input variables\n", "\n", @@ -204,7 +260,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-34acac6dd514c884", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "How does this work? \n", "\n", @@ -215,7 +280,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-53fd3c06a26175a3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.2** Copy your code from exercise 2.1 (but then with just normal indentation using tabs) into the cell below and change it such that it uses a function with input parameters to achieve the same task." ] @@ -233,7 +307,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-581873fe20420dc6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Functions with multiple inputs \n", "\n", @@ -270,16 +353,18 @@ "print_status3(a, 2*a)" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-78e05b5cbe11902b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.3** Make a new function `print_status4()` that takes three variables as arguments and prints out messages telling the user the values of each of them (as above, but with three input variables). Test it to make sure it works. " ] @@ -297,7 +382,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-fbf7e40e800af968", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Functions that return a value\n", "\n", @@ -319,7 +413,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3da704c82b41c669", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "To \"capture\" the value returned by the function, you can assign it to a varible like this:" ] @@ -336,7 +439,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c4eb4137a0421531", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can also just directly \"use\" the result of the function if you want:" ] @@ -352,7 +464,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9710d6768449ded8", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note that as soon as python sees the `return` command, it stops running the function, so any code after it will not be executed:" ] @@ -373,14 +494,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-aef6b10798831fa6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "If you want to send back more than one result to the user of your function, you can separate the results with commas when you use the `return` command. How do you make use of these two variables that you send back? You will explore this in this exercise:" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c20b50841a685607", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.4** **(a)** Write a function that takes two real numbers as input and returns the sum and product of the two numbers. In your function, try to send *both* of the calculated numbers back as a return value. We have not taught you that yet so you will have to look it up: I recommend trying a google search for \"python function return two variables\". " ] @@ -400,7 +539,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-35103493f788b1d5", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**(b)** Now USE your function to calculate the sum and product of `a` and `b`, \"capturing\" the sum and product in variables `s` and `p`:" ] @@ -422,7 +570,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-af1bcb8c3a7ded79", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Importing functions from libraries\n", "\n", @@ -458,7 +615,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6ebea512349a5084", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can see it has been imported by using the `%whos` command: " ] @@ -476,7 +642,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6d7e43494d90fc23", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Once it has been imported, you can access all the functions of the module by adding `time.` in front of the function name (from the time module) in your code:" ] @@ -496,9 +671,18 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-36d70980cc576500", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ - "If you import the whole module, you will have access to all the functions in it. To see what functions are in the module for you to use type `dir(sleep)`, which will generate this list.\n", + "If you import the whole module, you will have access to all the functions in it. To see what functions are in the module for you to use type `dir(time)`, which will generate this list.\n", "\n", "Sometimes, if you will be using the functions from the module a lot, you can give it a different \"prefix\" to save yourself some typing:" ] @@ -517,14 +701,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f6efa5c8f5b4ef63", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "We will use this a lot when using the `numpy` module, shortening its name to `np` when we import it, and also for the `matplotlib.pyplot` submodule, which we will shorten to `plt`. (These are also typically used conventions in the scientific community.)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7f7189cfda3188a5", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Importing a single function \n", "\n", @@ -542,7 +744,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b9ce119a2966790a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "When you do this, the function `sleep()` will be available directly in your notebook kernel \"namespace\" without any prefix:" ] @@ -562,7 +773,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d62c06ba6b0ce825", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Using `%whos`, we can now see that we have three different ways to use the `sleep()` function:" ] @@ -580,7 +800,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6f0445a2973bf90c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "If you look around on the internet, you will also find people that will do the following\n", "\n", @@ -599,7 +828,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-59161c880f8689e6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Shift-Tab for getting help\n", "\n", @@ -621,7 +859,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7daff037eb77301f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can also find the same help as the output of a code cell by using the `help()` function:" ] @@ -637,14 +884,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c7820259378fabba", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "There are extensive online resources for many modules. The most used modules have helpful examples on the functions and how to implement them. " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3c31a778f00cba2b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.5 (a)** Find help for the built-in functions `abs`, `int`, and `input`. Which of the help functions are easy to read? Which one does not provide such useful information (compared to the online documentation page)? (Put each help command in a separate cell)" ] @@ -684,7 +949,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-76f8da331b6edf11", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**(b)** Import the function `glob` from the library `glob` and print its help information. What does the function `glob(\"../*\")` do? " ] @@ -709,7 +983,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-10519772f1b5d68a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Global variables, local variables, and variable scope\n", "\n", @@ -754,7 +1037,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b4bb5a3316fe89c9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In this example, when Python is inside the function `my_func()`, it first looks to see if there is a variable `a1` in the local scope of the function. It does not find one, so it then goes and looks in the global scope. There, it finds a variable `a1`, and so it uses this one. \n", "\n", @@ -778,7 +1070,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-10458e43f3723d0b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "This code gives a `NameError` because there is no variable `b1` yet created in the global scope. If we run the following code cell and try the code above again, it will work. " ] @@ -794,7 +1095,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6074899777a25fae", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Here you can see one of risks of languages like python: because of the persistent memory of the kernel, code can succeed or fail depending on what code you have run before it...\n", "\n", @@ -812,7 +1122,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e5003608c97f5668", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Example 3** Variables defined in the local scope of a function are not accessible outside the function" ] @@ -835,7 +1154,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d65b38344a2c4136", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Example 4** Variables passed to functions cannot be modified by the function (more on this later when we look at more complicated data structures...sometimes this is different)" ] @@ -858,14 +1186,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7f9a0c6f80607325", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "This one is a bit subtle (mega-confusing?) because we re-used the same name `a` for the local variable in the function as the global variable outside of the function. However, the operation is quite logical. When the function code starts running, it creates a `local` variable `a` to store the value it received. And now, because there is already a local variable called `a`, using `a` in the function refers to the `local` variable `a`, not the `global` variable `a` we define before calling the function. " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5aa7c9bac0e183c9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Example 5** This one is a tricky one." ] @@ -888,14 +1234,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f637454870e0a4ec", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ " It would seem that the function would refer to the global variable `a` and therefore change it's value. However, it is tricky since we first use `a` in the function in an assignment. An assignment in python will automatically create a variable if it does not exist, and so python creates a new variable named `a` in the local scope. The name `a` inside the function now refers to this newly created local variable, and therefore the global variable will not be changed. In fact, this guarantees that you cannot change global variables inside a function, unless you use the `global` qualifier shown in the next example." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a2c1c2099359531d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Example 6** If you want to make sure that the `a` inside your function is referring to the global variable `a`, you can include the line `global a` inside your function to tell python that you mean the global variable `a`. " ] @@ -919,7 +1283,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b86006c7d643e2ba", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note that in general, it is considered bad programming practice to use (too many) global variables. Why? When you write longer and bigger sections of code, it is easier to understand what is going in in your function if your function uses only local variables and communicates back and forth using input parameter and return variables. Using too many global variables in a function can be confusing because they are defined in a different place in your code and so you don't have a good oversight of them. (Bigger projects can easily have 10,000+ lines of code!) \n", "\n", @@ -938,14 +1311,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-98aa5a09da4cf9c1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Solutions to Exercises" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3c74f16c8b097100", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.1** " ] @@ -954,6 +1345,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-776d867dd1e96a8e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -975,7 +1374,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-63bcaf171511d7a6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.2** " ] @@ -984,6 +1392,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3653f8a5e3438645", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -995,7 +1411,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4e6d55d796044deb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.3** " ] @@ -1004,6 +1429,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ab5a53f50aeb9eb2", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1018,7 +1451,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6b6a2cfb8f2059c4", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.4** **(a)** " ] @@ -1027,6 +1469,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-abb8b6adcc6f783b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1038,7 +1488,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5741495ca1f6ef79", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**(b)** " ] @@ -1046,7 +1505,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-df8f4cc5c689e227", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "a=1.5\n", @@ -1060,7 +1528,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c0897d2e0d18ff59", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.5** **(a)**" ] @@ -1068,7 +1545,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2eb1a4094c49679f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "help(abs)" @@ -1077,7 +1563,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6aed86aac101cb2a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "help(int)" @@ -1087,6 +1582,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-114d997d97a70ea6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1096,7 +1599,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a1e85a52b4b3a511", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**(b)**" ] @@ -1104,7 +1616,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-440e78ae4effd486", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "from glob import glob\n", @@ -1114,7 +1635,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a4cc244f58f0c6a1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "glob(\"../*\")\n", @@ -1124,6 +1654,7 @@ } ], "metadata": { + "celltoolbar": "Create Assignment", "jupytext": { "formats": "ipynb,md" }, @@ -1142,7 +1673,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.5" }, "toc": { "base_numbering": "2", @@ -1160,7 +1691,7 @@ "width": "430.594px" }, "toc_section_display": true, - "toc_window_display": true + "toc_window_display": false } }, "nbformat": 4, diff --git a/Notebook 2/Notebook 2 Functions.md b/Notebook 2/Notebook 2 Functions.md index 7cc6107e20b71d926a67fe786838f8f35e0fb8eb..5d8c26f43023c5c24e9377793fac0966979b2fec 100644 --- a/Notebook 2/Notebook 2 Functions.md +++ b/Notebook 2/Notebook 2 Functions.md @@ -5,14 +5,15 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: '1.1' - jupytext_version: 1.2.2 + format_version: '1.2' + jupytext_version: 1.3.0 kernelspec: display_name: Python 3 language: python name: python3 --- +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-98b92489332760b6"} --> # Functions in Python In this notebook, we will explore the implementation of functions in Python. @@ -21,10 +22,12 @@ In this notebook, we will explore the implementation of functions in Python. * Student is able to define functions with input parameters to execute a piece of code (use functions as "methods") * Student is able to create and use functions that return a value (or multiple values) -* Student is able to import functions from libraries / modules +* Student is able to import and use functions from libraries / modules * Student is able to use Shift-Tab to bring up the help for a function from a library * Student is able to predict if a variable name in a function refers to a local variable or a global variable +From here, the exercises get a bit more complicated. The solutions for the exercises in this notebook, and future ones, can be found at the bottom of the notebook, so that you can check your answer or have a peek if you are getting stuck. + ## Functions to save typing In programming, you often want to repeat the same sequence of commands over and over again. @@ -34,6 +37,7 @@ One way to do this is to copy and paste the same piece of code over and over aga For this reason (among others), programming languages allow programmers to define "functions". Functions are pieces of code that you can give a name and then enable you to them use over and over again, without having to retype the code text. As an example, let's say that we want to print out the value of a variables named `a` and `b` using a long sentence: +<!-- #endregion --> ```python a = 6 @@ -62,7 +66,7 @@ print("The value of variable a is", a) print("The value of variable b is", b) ``` -<!-- #region --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-fb10310a1ec1a391"} --> To save a lot of typing, one can define a simple function to do this work for us. To define a function, you use the following syntax: ``` @@ -86,9 +90,11 @@ def test_tab_and_shift_tab(): and shift-tab ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c16ac57175c4a00e"} --> Once you have defined your function, you can execute it by using the code `function_name()`. Let's look at how to use a function as a "procedure" to simplify the code above: +<!-- #endregion --> ```python def print_status(): @@ -116,12 +122,15 @@ b = 1 print_status() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a12ce8709fcf3b9f"} --> In this example, it may not be such a big deal, but you can imagine that as the code in your function becomes more and more complicated, it will save you a lot of time. Also, imagine that I wanted to change the wording of the sentence I print: in the case with the function, I would only have to do this once, while in the example without function, I would have to manually change this at 5 different places. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-cc004d60a3c3b17c"} --> **Exercise 2.1** Write your own function that contains two lines of code. The first line should make a new variable `var2` that is `var` converted to an integer. The second line of your code should print the value of `var2`. Using this code, play around with the indentation (add extra tabs and spaces for example) to see how 'critical' Python is with indentation. For example: does three spaces work instead of `Tab`? Does one space work? What about `Tab` on the first line and three spaces on the second line? Can you make Python trigger an `IdentationError`? +<!-- #endregion --> ```python var=3.5 @@ -129,7 +138,7 @@ var=3.5 # Your function here ``` -<!-- #region --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1be65f1712abb44d"} --> ## Functions with input variables Let's say that we wanted to print out the status of variables that we do not know the name of ahead of time, as in the example above. Say we wanted to make a function that could print out a message with the status of value of ANY variable. How could we do this? @@ -159,20 +168,23 @@ print_status2(a) print_status2(1.5323) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-34acac6dd514c884"} --> How does this work? When the function `print_status(a)` is called, Python "sends" ("passes" in computer speak) the value of `a` to the function. Inside the function, Python creates a new (temporary) variable called `x`, that is defined ONLY while the function code is running. This temporary variable `x` is then assigned the value that was sent to the function, and then the code is executed. When the function is finished, the variable `x` is destroyed. (Try adding the code `print(x)` above outside the function and see what happens!) Note, as you can see in the third example, the things you pass to functions do not even need to be variables! This is fine because the function only needs the value of the argument that is passed to the function. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-53fd3c06a26175a3"} --> **Exercise 2.2** Copy your code from exercise 2.1 (but then with just normal indentation using tabs) into the cell below and change it such that it uses a function with input parameters to achieve the same task. +<!-- #endregion --> ```python # Your code here ``` -<!-- #region --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-581873fe20420dc6"} --> ## Functions with multiple inputs Functions can also take multiple input variables. To do this, you put them all in between the brackets `()`, separated by commas. For example, with 3 variables, the syntax is: @@ -203,21 +215,21 @@ print_status3(2.5,1.5) print_status3(a, 2*a) ``` -```python - -``` - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-78e05b5cbe11902b"} --> **Exercise 2.3** Make a new function `print_status4()` that takes three variables as arguments and prints out messages telling the user the values of each of them (as above, but with three input variables). Test it to make sure it works. +<!-- #endregion --> ```python # Your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-fbf7e40e800af968"} --> ## Functions that return a value In addition to receiving values as inputs, functions can also send back values to the person using the function. In computer programming, this is called the "return value". When you create a function, you can use the `return` command to specify what value should be sent back to the person using the function. Let's look at an example: +<!-- #endregion --> ```python def my_formula(x): @@ -225,20 +237,26 @@ def my_formula(x): return y ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3da704c82b41c669"} --> To "capture" the value returned by the function, you can assign it to a varible like this: +<!-- #endregion --> ```python result = my_formula(3.5) print(result) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c4eb4137a0421531"} --> You can also just directly "use" the result of the function if you want: +<!-- #endregion --> ```python print(my_formula(4.6)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-9710d6768449ded8"} --> Note that as soon as python sees the `return` command, it stops running the function, so any code after it will not be executed: +<!-- #endregion --> ```python def myfunction(x): @@ -249,10 +267,13 @@ def myfunction(x): print(myfunction(5)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-aef6b10798831fa6"} --> If you want to send back more than one result to the user of your function, you can separate the results with commas when you use the `return` command. How do you make use of these two variables that you send back? You will explore this in this exercise: +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c20b50841a685607"} --> **Exercise 2.4** **(a)** Write a function that takes two real numbers as input and returns the sum and product of the two numbers. In your function, try to send *both* of the calculated numbers back as a return value. We have not taught you that yet so you will have to look it up: I recommend trying a google search for "python function return two variables". +<!-- #endregion --> ```python # Your function here @@ -260,7 +281,9 @@ def product_and_sum(...): ... ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-35103493f788b1d5"} --> **(b)** Now USE your function to calculate the sum and product of `a` and `b`, "capturing" the sum and product in variables `s` and `p`: +<!-- #endregion --> ```python a=1.5 @@ -272,6 +295,7 @@ print("Sum is:", s) print("Product is:", p) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-af1bcb8c3a7ded79"} --> ## Importing functions from libraries One of the big advantages of python is that there are huge collection of libraries that include code for doing a huge number of things for you! We will make extensive use of the library `numpy` for numerical calculations in Python, and the library `matplotlib` for generating scientific plots. Beyond this, nearly anything you want to be able to do on a computer can be found in Python libraries, which is one of the reasons it is so popular. @@ -293,18 +317,23 @@ https://docs.python.org/3/library/time.html#time.sleep ### Importing a whole module The simplest way to be able use the `sleep` function of the `time` module is to import it using the following command: +<!-- #endregion --> ```python import time ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6ebea512349a5084"} --> You can see it has been imported by using the `%whos` command: +<!-- #endregion --> ```python %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6d7e43494d90fc23"} --> Once it has been imported, you can access all the functions of the module by adding `time.` in front of the function name (from the time module) in your code: +<!-- #endregion --> ```python print("Starting to sleep") @@ -312,9 +341,11 @@ time.sleep(5) print("Done!") ``` -If you import the whole module, you will have access to all the functions in it. To see what functions are in the module for you to use type `dir(sleep)`, which will generate this list. +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-36d70980cc576500"} --> +If you import the whole module, you will have access to all the functions in it. To see what functions are in the module for you to use type `dir(time)`, which will generate this list. Sometimes, if you will be using the functions from the module a lot, you can give it a different "prefix" to save yourself some typing: +<!-- #endregion --> ```python import time as tm @@ -323,18 +354,23 @@ tm.sleep(5) print("Done!") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f6efa5c8f5b4ef63"} --> We will use this a lot when using the `numpy` module, shortening its name to `np` when we import it, and also for the `matplotlib.pyplot` submodule, which we will shorten to `plt`. (These are also typically used conventions in the scientific community.) +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7f7189cfda3188a5"} --> ### Importing a single function If you need only a single function from a library, there is also a second commonly used way to import only that single function using the following syntax: +<!-- #endregion --> ```python from time import sleep ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b9ce119a2966790a"} --> When you do this, the function `sleep()` will be available directly in your notebook kernel "namespace" without any prefix: +<!-- #endregion --> ```python print("Starting to sleep") @@ -342,13 +378,15 @@ sleep(5) print("Done!") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d62c06ba6b0ce825"} --> Using `%whos`, we can now see that we have three different ways to use the `sleep()` function: +<!-- #endregion --> ```python %whos ``` -<!-- #region --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6f0445a2973bf90c"} --> If you look around on the internet, you will also find people that will do the following ``` @@ -364,6 +402,7 @@ If you import both of them, you will overwrite these functions by the second imp For these reasons, it is generally advised not to use `import *`, and it is considered poor coding practice in modern python. <!-- #endregion --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-59161c880f8689e6"} --> ### Shift-Tab for getting help Like the tab completion we saw in the first notebook, Jupyter also can give you help on functions you have imported from libraries if you type `Shift-Tab`. @@ -371,21 +410,27 @@ Like the tab completion we saw in the first notebook, Jupyter also can give you Say I forgot how to use the `sleep()` function. If I type the word "sleep" and then push `Shift-Tab`, Jupyter will bring up a help window for that function. Try it: click on any part of the word `sleep` in the following code cell and push `Shift-Tab`: +<!-- #endregion --> ```python sleep ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7daff037eb77301f"} --> You can also find the same help as the output of a code cell by using the `help()` function: +<!-- #endregion --> ```python help(sleep) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c7820259378fabba"} --> There are extensive online resources for many modules. The most used modules have helpful examples on the functions and how to implement them. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3c31a778f00cba2b"} --> **Exercise 2.5 (a)** Find help for the built-in functions `abs`, `int`, and `input`. Which of the help functions are easy to read? Which one does not provide such useful information (compared to the online documentation page)? (Put each help command in a separate cell) +<!-- #endregion --> ```python # Your code here @@ -399,7 +444,9 @@ There are extensive online resources for many modules. The most used modules hav # Your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-76f8da331b6edf11"} --> **(b)** Import the function `glob` from the library `glob` and print its help information. What does the function `glob("../*")` do? +<!-- #endregion --> ```python # run the help here @@ -409,6 +456,7 @@ There are extensive online resources for many modules. The most used modules hav # your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-10519772f1b5d68a"} --> ## Global variables, local variables, and variable scope In our first functions above, we saw a couple of examples of using variables inside functions. @@ -432,6 +480,7 @@ If you want to create a global variable inside a function, or make sure the vari Let's take a look at this in more detail by analysing a few examples. **Example 1** Accessing a global variable inside a function +<!-- #endregion --> ```python a1 = 5 @@ -444,7 +493,7 @@ a1 = 6 my_func() ``` -<!-- #region --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b4bb5a3316fe89c9"} --> In this example, when Python is inside the function `my_func()`, it first looks to see if there is a variable `a1` in the local scope of the function. It does not find one, so it then goes and looks in the global scope. There, it finds a variable `a1`, and so it uses this one. @@ -458,21 +507,27 @@ def my_func(): my_func() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-10458e43f3723d0b"} --> This code gives a `NameError` because there is no variable `b1` yet created in the global scope. If we run the following code cell and try the code above again, it will work. +<!-- #endregion --> ```python b1 = 6 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6074899777a25fae"} --> Here you can see one of risks of languages like python: because of the persistent memory of the kernel, code can succeed or fail depending on what code you have run before it... If you want to see the error message above again, you can delete variable b1 using this code and run it again: +<!-- #endregion --> ```python del b1 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-e5003608c97f5668"} --> **Example 3** Variables defined in the local scope of a function are not accessible outside the function +<!-- #endregion --> ```python def my_func(): @@ -483,7 +538,9 @@ my_func() print(x) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d65b38344a2c4136"} --> **Example 4** Variables passed to functions cannot be modified by the function (more on this later when we look at more complicated data structures...sometimes this is different) +<!-- #endregion --> ```python def my_func(a): @@ -494,10 +551,13 @@ my_func(a) print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7f9a0c6f80607325"} --> This one is a bit subtle (mega-confusing?) because we re-used the same name `a` for the local variable in the function as the global variable outside of the function. However, the operation is quite logical. When the function code starts running, it creates a `local` variable `a` to store the value it received. And now, because there is already a local variable called `a`, using `a` in the function refers to the `local` variable `a`, not the `global` variable `a` we define before calling the function. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5aa7c9bac0e183c9"} --> **Example 5** This one is a tricky one. +<!-- #endregion --> ```python a = 6 @@ -510,10 +570,13 @@ my_func() print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f637454870e0a4ec"} --> It would seem that the function would refer to the global variable `a` and therefore change it's value. However, it is tricky since we first use `a` in the function in an assignment. An assignment in python will automatically create a variable if it does not exist, and so python creates a new variable named `a` in the local scope. The name `a` inside the function now refers to this newly created local variable, and therefore the global variable will not be changed. In fact, this guarantees that you cannot change global variables inside a function, unless you use the `global` qualifier shown in the next example. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a2c1c2099359531d"} --> **Example 6** If you want to make sure that the `a` inside your function is referring to the global variable `a`, you can include the line `global a` inside your function to tell python that you mean the global variable `a`. +<!-- #endregion --> ```python a = 6 @@ -527,6 +590,7 @@ my_func() print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b86006c7d643e2ba"} --> Note that in general, it is considered bad programming practice to use (too many) global variables. Why? When you write longer and bigger sections of code, it is easier to understand what is going in in your function if your function uses only local variables and communicates back and forth using input parameter and return variables. Using too many global variables in a function can be confusing because they are defined in a different place in your code and so you don't have a good oversight of them. (Bigger projects can easily have 10,000+ lines of code!) In computer science, this is a topic of often intense debate (resulting in what nerds refer to as a <a href="https://www.urbandictionary.com/define.php?term=flame%20war">flame war</a>), with global variables being branded as "dangerous" like in this stack exchange post: @@ -540,14 +604,17 @@ Summary of the rules for global and local variables: * If a local variable of the same name exists or is created by python (by assignment, for example), then python uses the local varible * If you try to use a variable name that does not exist locally, python checks for a global variable of the same name * If you want to change the value of a global inside a function, then you must use the `global` statement to make it clear to python than you want that name to refer to the global variable +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-98aa5a09da4cf9c1"} --> ## Solutions to Exercises +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3c74f16c8b097100"} --> **Exercise 2.1** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-776d867dd1e96a8e"} var=3.5 # Example: one space is actually enough! But is discouraged, as you can see @@ -563,17 +630,21 @@ def myfunction(): print(var2) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-63bcaf171511d7a6"} --> **Exercise 2.2** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3653f8a5e3438645"} def myfunction(var): var2 = int(var) print(var2) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4e6d55d796044deb"} --> **Exercise 2.3** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ab5a53f50aeb9eb2"} def print_status3(x, y, z): print("The value of the first input variable is ", x) print("The value of the second input variable is ", y) @@ -582,17 +653,21 @@ def print_status3(x, y, z): print(1,2,3) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6b6a2cfb8f2059c4"} --> **Exercise 2.4** **(a)** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-abb8b6adcc6f783b"} # Your function here def product_and_sum(a,b): return a*b, a+b ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5741495ca1f6ef79"} --> **(b)** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-df8f4cc5c689e227"} a=1.5 b=2.5 @@ -602,28 +677,32 @@ print("Sum is:", s) print("Product is:", p) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c0897d2e0d18ff59"} --> **Exercise 2.5** **(a)** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2eb1a4094c49679f"} help(abs) ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6aed86aac101cb2a"} help(int) ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-114d997d97a70ea6"} help(input) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a1e85a52b4b3a511"} --> **(b)** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-440e78ae4effd486"} from glob import glob help(glob) ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a4cc244f58f0c6a1"} glob("../*") # It returns list of files and folders in the parent directory of that # in which this notebook is stored diff --git a/Notebook 3/Notebook 3 Program Flow Control.ipynb b/Notebook 3/Notebook 3 Program Flow Control.ipynb index d4afc4189ac0333df873f64a9d2504304d5be656..7705dd764f84ecb7e5ff045db7533a4d5d2c5d93 100644 --- a/Notebook 3/Notebook 3 Program Flow Control.ipynb +++ b/Notebook 3/Notebook 3 Program Flow Control.ipynb @@ -2,7 +2,16 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-820ab9eec85e7818", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "# Program flow control with Conditional Statements and Loops\n", "\n", @@ -80,7 +89,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8f5002353d747ef9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.1** Try out the following conditional statements, filling in the values specified to replace the `---` in the code cells.\n", "\n", @@ -101,7 +119,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-932ea824ec7ca25c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Check if `103.51` is true:" ] @@ -120,7 +147,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b4c0600a205af8d1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Check if `-1` is true:" ] @@ -139,7 +175,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-06bb622a4a2db31e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Check if condition statements work with boolean variables:" ] @@ -159,7 +204,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f2462c37fb4f8c16", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Check if conditional statements work with numerical variables:" ] @@ -179,7 +233,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ad422752c90b5a9e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Comparison and test operators\n", "\n", @@ -269,14 +332,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2c4696ee3922ab23", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In addition to the `==` operator, there are also several other comparison operators such as `<`, `>`, `>=`, `<=`, `!=`" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f6b4a7e98c6672e0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.2** Test the operators `<`, `>`, `>=`, `<=`, `!=` by trying them in an if statement with numerical values. " ] @@ -343,14 +424,23 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-096011724723bedd", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Logical operations\n", "\n", "Python also allows you to build the `expression` out of logical combinations of several conditions using the keywords `and`, `or`, and `not`. The value of these operators is as follows\n", "* `and` evaluates to True if both conditions are True\n", "* `or` evaluates to True if either condition is True\n", - "* `not`evaluates to true if condition is not True\n", + "* `not`evaluates to True if condition is not True\n", "\n", "Below are a few examples. " ] @@ -407,7 +497,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8a0f0f9e5985f291", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.3** Try out the following examples using the if statement form from above for the conditions\n", "\n", @@ -427,7 +526,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-fbaf814587160898", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**(b)** Do you think that the statement `not False or True` evaluates to `True` or `False`? Try it out and see:" ] @@ -443,7 +551,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a09e89673e9b20d3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "To understand what happened in part (b), we have to know if Python first performs the operation `False or True` or if it performs the operation `not False` first. The rules for which order python does things in can be found in the documentation for <a href=https://docs.python.org/3/reference/expressions.html#operator-precedence>operator precedence</a>. In the example above, we can see that the `not` operator had precedence and python performed the `not` before it performed the `or`. \n", "\n", @@ -462,13 +579,22 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e8a75bf958008fea", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### The `elif` and `else` statements\n", "\n", "In Python, you can combine the `if` statement with `elif` and `else` commands in a chain in order to allow you to take actions in the case that the starting `if` statement was false. \n", "\n", - "`elif` (else if) is a command that allows you to check another condition if the condition of the starting `if` is `False`. Note that if the `if` criterion is `True`, the `elif` statement will not be skipped and not checked. \n", + "`elif` (else if) is a command that allows you to check another condition if the condition of the starting `if` is `False`. Note that if the `if` criterion is `True`, the `elif` statement will be skipped and not checked. \n", "\n", "`else` is a command that allows you to execute some code if all of the `if` and all the `elif`s are are `False`. \n", "\n", @@ -517,7 +643,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-bcd1475b5312a9de", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Since the code inside your `if` code block is just regular code, you can also add another if statement inside that code block. This creates a <a href=https://en.wikipedia.org/wiki/Nesting_(computing)>nested</a> `if` statement inside your first one:" ] @@ -541,9 +676,18 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ae510a3a63768c64", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ - "**Exercise 3.4** Practice the use of the if-elif-else statement with the following exercise by filling in the missing conditional statements. You must use all three of `if`, `elif` and `else`. " + "**Exercise 3.4** Practice the use of the if-elif-else statement with the following exercise by filling in the missing conditional statements. You must use all three of `if`, `elif` and `else`. You also can not use the `end` operator." ] }, { @@ -572,7 +716,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-636e6a2b17097f10", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Loops\n", "\n", @@ -608,7 +761,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-84200ae77714a458", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In this example here, we use a while loop to add up all the numbers between 1 and 10:" ] @@ -631,7 +793,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b97c9b714150c6b6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note that with `while` loops, you have to be careful to make sure that your code block does something that results in your `expression` becomes false at some point, otherwise the loop will run for ever. \n", "\n", @@ -655,14 +826,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4d529b9055631e33", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**This code will never finish: it will go into an infinite loop!** (You can see that it is still running because the `*` beside the `In` text never gets turned into a number.) Note that for this will have to manually stop the kernel using the stop button in the toolbar or it will run forever..." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-bb322dca784c930d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### When should I use a `while` loop? \n", "\n", @@ -698,14 +887,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-98fdacbdf316c332", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note here I have also used a new operator `+=`: this is a special assignment operator that increments the variable by the specified amount. `a += 1` is equivalent to `a = a + 1` (it just saves a bit of typing, remember: programmers are lazy...). " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8419ddf039469350", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.5** Write a function to calculate the factorial of a number using a while loop. The factorial, denoted with !, is the product of all numbers up to that number, i.e. $4!=1\\cdot2\\cdot3\\cdot4$. Note that perhaps unexpectedly for you 0!=1 for which your function also have to give an answer.\n", "\n", @@ -728,7 +935,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8bac96ac75f3228d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### The `for` loop\n", "\n", @@ -761,7 +977,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2fa53e57cfc647d0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.6** Calculate the sum $\\sum_{i=0}^n\\sin^2(i)$ for n=10 using a `for` loop." ] @@ -780,7 +1005,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-58d187a8c4c8dc2e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### The `range()` function\n", "\n", @@ -809,7 +1043,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a1e795446fc32a60", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "#### `range(N,M)`: Print a range of numbers starting from N and ending at M-1\n", "\n", @@ -828,7 +1071,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-adce08f894188b15", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You might ask: why not stop at `M`? If you say the words \"range from N to M\", you would think that this range should include M? \n", "\n", @@ -842,7 +1094,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3f4edd12e92245a9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "#### `range(N,M,S)`: Print a range of numbers less than M, starting from N, with steps of S\n", "\n", @@ -861,14 +1122,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a67520608225fbaf", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note that the `range` function works only with integers: `range(1,11,0.5)` is not allowed. (For floating point ranges, you can use the `numpy` function `arange`, more on that later...)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-95d40298b2f365ae", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.7** Calculate the sum of all numbers from 1 to 100 using a `for` loop with the `range()` function. Compare it to the famous value that Gauss calculated in class as an <a href=https://nrich.maths.org/2478>elementary school student</a>. " ] @@ -886,7 +1165,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3cf16acbe0d81ace", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Using `for` loops with things other than ranges of numbers\n", "\n", @@ -916,7 +1204,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-55311578c51712a5", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Manually exiting or skipping a loop using `break` and `continue`\n", "\n", @@ -944,7 +1241,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ebe183bb239808e9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "It looks funny at first, because `while True` looks like you will loop forever! But of course, you are saved by the `break` statement. \n", "\n", @@ -980,7 +1286,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5c5df9248db93ce5", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "The statement `continue` is used if you want to skip the rest of the code in the code block and restart the next loop. This can sometimes be useful if as a way of avoiding adding an `else` statement. (Remember, programmers are lazy typers...and it can be useful if you have a big long complicated block of code...)" ] @@ -1003,14 +1318,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8a7bc126b9fe430a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "This is probably not a great example (if you are smart you can do this with less typing), but in any case, you now know what `continue` does. " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-10e1b8eed7f40b1c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.8** Write code that creates a variable `i` with an initial value of zero. Use a while loop that increments `i` by one as long as `i` is less than one million. Have your loop stop if `i` satisfies the condition $i(i-10) = 257024$. Have your code print the `i` that satisfies this condition (if there is one). " ] @@ -1028,14 +1361,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-38744ad3ab4ca87f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Solutions to exercises" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6f337f4358a682a0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.1** " ] @@ -1044,6 +1395,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5b0313b8e8598466", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1056,6 +1415,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1e3641fb9a779721", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": false }, "outputs": [], @@ -1068,6 +1435,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4ceedbf1a3c4d051", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1080,6 +1455,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-cc4df8371d0432b9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1093,6 +1476,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-123947c9011d4062", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1104,7 +1495,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9e3b271232e2220d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.2**" ] @@ -1113,6 +1513,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a6ac2b6dc510f1aa", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1125,6 +1533,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-51027497694978b9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1137,6 +1553,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1e264535ded80168", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1149,6 +1573,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ed3481fa5c1ce2aa", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1161,6 +1593,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a8414c045994a401", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1172,7 +1612,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4271a640dc4e16fa", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.3** **(a)** " ] @@ -1181,6 +1630,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-40cec335e293301f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1191,7 +1648,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b47fd4e7bdeeb7ed", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**(b)** " ] @@ -1199,7 +1665,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7b133c4093f3de42", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "if not False or True:\n", @@ -1210,7 +1685,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2b945c67ff612ec2", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.4** " ] @@ -1219,6 +1703,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-36ba5da4827cc9c8", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1243,7 +1735,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a03f32b890804dc7", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.5** " ] @@ -1251,7 +1752,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6ad76460fb6dee8a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "def factorial(a):\n", @@ -1271,7 +1781,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b7807a055420b17b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.6** " ] @@ -1280,6 +1799,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1536f4bee510c9ac", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1295,7 +1822,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-361135ad0db80c76", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.7** " ] @@ -1304,13 +1840,21 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4dc583cec91521e9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], "source": [ "s = 0\n", "\n", - "# Note than range should end at 101!\n", + "# Note the range should end at 101!\n", "# It should also start at 1: range(101) will also execute \n", "# the loop with i=0 (which won't matter here...)\n", "for i in range(1,101):\n", @@ -1321,7 +1865,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-21f178bba5a2f9ad", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.8** " ] @@ -1329,7 +1882,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-188d5c2433fb8aa3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "# Solution\n", @@ -1339,14 +1901,14 @@ "while i <= maxnum:\n", " if i*(i-10)==257024:\n", " break\n", - " else:\n", - " i+=1\n", - " \n", + " i+=1\n", + " \n", "print(i)" ] } ], "metadata": { + "celltoolbar": "Create Assignment", "jupytext": { "formats": "ipynb,md" }, @@ -1365,7 +1927,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.5" }, "toc": { "base_numbering": "3", @@ -1378,7 +1940,7 @@ "toc_cell": false, "toc_position": {}, "toc_section_display": true, - "toc_window_display": false + "toc_window_display": true } }, "nbformat": 4, diff --git a/Notebook 3/Notebook 3 Program Flow Control.md b/Notebook 3/Notebook 3 Program Flow Control.md index 3811393d9eefc02482a3359b127dd1931daa311b..98a265ef99fa333caaab823a8aca912d58346488 100644 --- a/Notebook 3/Notebook 3 Program Flow Control.md +++ b/Notebook 3/Notebook 3 Program Flow Control.md @@ -5,15 +5,15 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: '1.1' - jupytext_version: 1.2.2 + format_version: '1.2' + jupytext_version: 1.3.0 kernelspec: display_name: Python 3 language: python name: python3 --- -<!-- #region --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-820ab9eec85e7818"} --> # Program flow control with Conditional Statements and Loops In this notebook, we will learn how to control the flow of execution of python code using conditional statements and loops. @@ -68,30 +68,38 @@ if not False: print("not False is True") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8f5002353d747ef9"} --> **Exercise 3.1** Try out the following conditional statements, filling in the values specified to replace the `---` in the code cells. Check if the integer `1` is true: +<!-- #endregion --> ```python if ---: print('The expression is true') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-932ea824ec7ca25c"} --> Check if `103.51` is true: +<!-- #endregion --> ```python if ---: print('The expression is true') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b4c0600a205af8d1"} --> Check if `-1` is true: +<!-- #endregion --> ```python if ---: print('The expression is true') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-06bb622a4a2db31e"} --> Check if condition statements work with boolean variables: +<!-- #endregion --> ```python a = True @@ -99,7 +107,9 @@ if ---: print('The expression is true') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f2462c37fb4f8c16"} --> Check if conditional statements work with numerical variables: +<!-- #endregion --> ```python b = -73.445 @@ -107,11 +117,13 @@ if ---: print('The expression is true') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ad422752c90b5a9e"} --> ### Comparison and test operators In the above, the `expression` in the `if` statements were all directly values that are either True or false (except for the example of `not False`). More generally, however, the `expression` can also include comparisons. Some examples of numerical comparisons are given here below: +<!-- #endregion --> ```python if 5 == 5: @@ -157,10 +169,13 @@ if 2*a == 10: print("You can also use mathematical expressions") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2c4696ee3922ab23"} --> In addition to the `==` operator, there are also several other comparison operators such as `<`, `>`, `>=`, `<=`, `!=` +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f6b4a7e98c6672e0"} --> **Exercise 3.2** Test the operators `<`, `>`, `>=`, `<=`, `!=` by trying them in an if statement with numerical values. +<!-- #endregion --> ```python if --- < ---: @@ -187,14 +202,16 @@ if --- != ---: print('The expression is true') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-096011724723bedd"} --> ### Logical operations Python also allows you to build the `expression` out of logical combinations of several conditions using the keywords `and`, `or`, and `not`. The value of these operators is as follows * `and` evaluates to True if both conditions are True * `or` evaluates to True if either condition is True -* `not`evaluates to true if condition is not True +* `not`evaluates to True if condition is not True Below are a few examples. +<!-- #endregion --> ```python if True and False: @@ -221,39 +238,47 @@ if 5 < 6 and 10 > 9: print("An example of combining conditional expressions with 'and'") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8a0f0f9e5985f291"} --> **Exercise 3.3** Try out the following examples using the if statement form from above for the conditions **(a)** Check if both 5 is smaller than 6, and 10 is smaller equal than 9: +<!-- #endregion --> ```python # Your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-fbaf814587160898"} --> **(b)** Do you think that the statement `not False or True` evaluates to `True` or `False`? Try it out and see: +<!-- #endregion --> ```python # Your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a09e89673e9b20d3"} --> To understand what happened in part (b), we have to know if Python first performs the operation `False or True` or if it performs the operation `not False` first. The rules for which order python does things in can be found in the documentation for <a href=https://docs.python.org/3/reference/expressions.html#operator-precedence>operator precedence</a>. In the example above, we can see that the `not` operator had precedence and python performed the `not` before it performed the `or`. What if I wanted to have python perform the `or` first? You do this by enclosing `True or False` in brackets: +<!-- #endregion --> ```python if not (False or True): print("not (False or True) is False so this will not be printed") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-e8a75bf958008fea"} --> ### The `elif` and `else` statements In Python, you can combine the `if` statement with `elif` and `else` commands in a chain in order to allow you to take actions in the case that the starting `if` statement was false. -`elif` (else if) is a command that allows you to check another condition if the condition of the starting `if` is `False`. Note that if the `if` criterion is `True`, the `elif` statement will not be skipped and not checked. +`elif` (else if) is a command that allows you to check another condition if the condition of the starting `if` is `False`. Note that if the `if` criterion is `True`, the `elif` statement will be skipped and not checked. `else` is a command that allows you to execute some code if all of the `if` and all the `elif`s are are `False`. Note that to be part of the "chain", all the `elif`s and the last `else` must follow directly after each other's code blocks with no other code in between. And a new `if` always starts a new chain. You can see how this works in the following examples: +<!-- #endregion --> ```python a = 5 @@ -279,7 +304,9 @@ if a<6: print("unlike elif, a second if will get executed.") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-bcd1475b5312a9de"} --> Since the code inside your `if` code block is just regular code, you can also add another if statement inside that code block. This creates a <a href=https://en.wikipedia.org/wiki/Nesting_(computing)>nested</a> `if` statement inside your first one: +<!-- #endregion --> ```python # example of a nested if-statement @@ -293,7 +320,9 @@ else: print("none of these are the case") ``` -**Exercise 3.4** Practice the use of the if-elif-else statement with the following exercise by filling in the missing conditional statements. You must use all three of `if`, `elif` and `else`. +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ae510a3a63768c64"} --> +**Exercise 3.4** Practice the use of the if-elif-else statement with the following exercise by filling in the missing conditional statements. You must use all three of `if`, `elif` and `else`. You also can not use the `end` operator. +<!-- #endregion --> ```python def check_number(a): @@ -312,7 +341,7 @@ check_number(10) check_number(15) ``` -<!-- #region --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-636e6a2b17097f10"} --> ## Loops Loops are a construction in a programming language that allow you to execute the same piece of code repeatedly. @@ -340,7 +369,9 @@ while i <= 10: i = i+1 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-84200ae77714a458"} --> In this example here, we use a while loop to add up all the numbers between 1 and 10: +<!-- #endregion --> ```python i = 1 @@ -353,9 +384,11 @@ while i<= 10: print("Sum is", s) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b97c9b714150c6b6"} --> Note that with `while` loops, you have to be careful to make sure that your code block does something that results in your `expression` becomes false at some point, otherwise the loop will run for ever. For example, when initially I wrote the above cell to calculate the sum, I forgot the `i = i+1` line of code: +<!-- #endregion --> ```python i = 1 @@ -367,9 +400,11 @@ while i<= 10: print("Sum is", s) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4d529b9055631e33"} --> **This code will never finish: it will go into an infinite loop!** (You can see that it is still running because the `*` beside the `In` text never gets turned into a number.) Note that for this will have to manually stop the kernel using the stop button in the toolbar or it will run forever... +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-bb322dca784c930d"} --> ### When should I use a `while` loop? For both of the examples above, one would typically use `for` loop, which we will see in the next section. One place where `while` loops are very useful is if you do not know ahead of time how many iterations of the loop you will need. @@ -381,6 +416,7 @@ S(n) = \sum_{i=1}^n \sin^2(i) $$ Let's say we want to know: for which $n$ does the sum exceed 30? We can easily check this with a `while` loop: +<!-- #endregion --> ```python from numpy import sin @@ -396,12 +432,15 @@ print("Sum is", s) print("i is", i) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-98fdacbdf316c332"} --> Note here I have also used a new operator `+=`: this is a special assignment operator that increments the variable by the specified amount. `a += 1` is equivalent to `a = a + 1` (it just saves a bit of typing, remember: programmers are lazy...). +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8419ddf039469350"} --> **Exercise 3.5** Write a function to calculate the factorial of a number using a while loop. The factorial, denoted with !, is the product of all numbers up to that number, i.e. $4!=1\cdot2\cdot3\cdot4$. Note that perhaps unexpectedly for you 0!=1 for which your function also have to give an answer. *If your code doesn't seem to stop running, you may need to "debug" your code to see where your mistake is (I did). A handy way to do this, for example, is to add a `print(i)` statement inside your while loop: it will enable you to see if the variable `i` is doing what you think it should be...* +<!-- #endregion --> ```python def factorial(a): @@ -412,7 +451,7 @@ print(factorial(4)) print(factorial(0)) ``` -<!-- #region --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8bac96ac75f3228d"} --> ### The `for` loop The `for` loop is designed to execute a piece of code a fixed number of times. The syntax of the `for` loop is: @@ -435,13 +474,16 @@ for i in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10): print(s) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2fa53e57cfc647d0"} --> **Exercise 3.6** Calculate the sum $\sum_{i=0}^n\sin^2(i)$ for n=10 using a `for` loop. +<!-- #endregion --> ```python # Your code here for...: ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-58d187a8c4c8dc2e"} --> ### The `range()` function You can imagine that if we wanted to perform this sum up to 100, it would be very annoying to type out all of the numbers. For this, Python has a convenient function `range()`, than can automatically generate ranges of numbers for you! @@ -455,22 +497,26 @@ But let's look just at some concrete examples for now: If you give `range` only one argument, it will give a range of numbers starting from 0, and a total number of numbers determined by the argument. As an explicit example, `range(10)` is equivalent to `(0,1,2,3,4,5,6,7,8,9)`: +<!-- #endregion --> ```python for i in range(10): print(i) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a1e795446fc32a60"} --> #### `range(N,M)`: Print a range of numbers starting from N and ending at M-1 If you give `range` two arguments `range(N,M)`, it will give a range of numbers starting from `N` and stopping at `M-1`. +<!-- #endregion --> ```python for i in range(1,11): print(i) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-adce08f894188b15"} --> You might ask: why not stop at `M`? If you say the words "range from N to M", you would think that this range should include M? There are two reasons I can think of: @@ -480,26 +526,32 @@ There are two reasons I can think of: Maybe there are more, I'm not sure (you'd have to ask the nerds who wrote Python...). But in any case, you just need to remember that `range(N,M)` stops at `M-1`... +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3f4edd12e92245a9"} --> #### `range(N,M,S)`: Print a range of numbers less than M, starting from N, with steps of S This is like the last one, but now with the chance to change the steps: +<!-- #endregion --> ```python for i in range(1,11,2): print(i) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a67520608225fbaf"} --> Note that the `range` function works only with integers: `range(1,11,0.5)` is not allowed. (For floating point ranges, you can use the `numpy` function `arange`, more on that later...) +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-95d40298b2f365ae"} --> **Exercise 3.7** Calculate the sum of all numbers from 1 to 100 using a `for` loop with the `range()` function. Compare it to the famous value that Gauss calculated in class as an <a href=https://nrich.maths.org/2478>elementary school student</a>. +<!-- #endregion --> ```python # Your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3cf16acbe0d81ace"} --> ### Using `for` loops with things other than ranges of numbers In the examples above, we looked at using `for` loops to iterate through a list of integers. @@ -507,6 +559,7 @@ In the examples above, we looked at using `for` loops to iterate through a list In Python, however, `for` loops are much more flexible than only iterating over numbers: `for` loops can iterate over any <a href=https://www.programiz.com/python-programming/iterator>iteratable object</a>, including 1-D numpy arrays, which we will see in later notebooks. But, as an example, here is piece of code that uses the `numpy` random number generator to calculate the sum of 10 random integers between 0 and 100: +<!-- #endregion --> ```python from numpy.random import randint @@ -520,11 +573,13 @@ print() print("Sum is ", s) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-55311578c51712a5"} --> ### Manually exiting or skipping a loop using `break` and `continue` In addition to the examples we saw above, Python offers to extra commands for controlling the flow of execution in a loop: `break` and `continue` `break` is a command you can use to force the exiting of either a `for` loop or a `while` loop. For example, you can replace the while loop above using something like this: +<!-- #endregion --> ```python s = 0 @@ -538,9 +593,11 @@ while True: print(s) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ebe183bb239808e9"} --> It looks funny at first, because `while True` looks like you will loop forever! But of course, you are saved by the `break` statement. Using `break` statements can sometimes make your code more readable, particularly if you want to be able to exit the loop under different conditions, or have an exist condition trigger a certain piece of code. Here is an example of two conditions: +<!-- #endregion --> ```python from numpy.random import randint @@ -564,7 +621,9 @@ else: print("We didn't find a 5 in the maximum number number of tries (", max_tries, ")") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5c5df9248db93ce5"} --> The statement `continue` is used if you want to skip the rest of the code in the code block and restart the next loop. This can sometimes be useful if as a way of avoiding adding an `else` statement. (Remember, programmers are lazy typers...and it can be useful if you have a big long complicated block of code...) +<!-- #endregion --> ```python s = 0 @@ -577,94 +636,108 @@ print("The sum of 30 random numbers between 0 and 30 excluding those that are di ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8a7bc126b9fe430a"} --> This is probably not a great example (if you are smart you can do this with less typing), but in any case, you now know what `continue` does. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-10e1b8eed7f40b1c"} --> **Exercise 3.8** Write code that creates a variable `i` with an initial value of zero. Use a while loop that increments `i` by one as long as `i` is less than one million. Have your loop stop if `i` satisfies the condition $i(i-10) = 257024$. Have your code print the `i` that satisfies this condition (if there is one). +<!-- #endregion --> ```python # Your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-38744ad3ab4ca87f"} --> ## Solutions to exercises +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6f337f4358a682a0"} --> **Exercise 3.1** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5b0313b8e8598466"} if 1: print('The expression is true') ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1e3641fb9a779721"} if 103.51: print('The expression is true') ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4ceedbf1a3c4d051"} if -1: print('The expression is true') ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-cc4df8371d0432b9"} a = True if a: print('The expression is true') ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-123947c9011d4062"} b = -73.445 if b: print('The expression is true') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-9e3b271232e2220d"} --> **Exercise 3.2** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a6ac2b6dc510f1aa"} if 5 < 5.1: print('The expression is true') ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-51027497694978b9"} if 5 <= 5: print('The expression is true') ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1e264535ded80168"} if 5.1 > 5.1: print('The expression is true') ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ed3481fa5c1ce2aa"} if 5.1 >= 5.1: print('The expression is true') ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a8414c045994a401"} # "!=" is "not-equals" if 5 != 5.1: print('The expression is true') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4271a640dc4e16fa"} --> **Exercise 3.3** **(a)** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-40cec335e293301f"} if 5 < 6 and 10 <= 9: print("i don't think this is true") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b47fd4e7bdeeb7ed"} --> **(b)** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7b133c4093f3de42"} if not False or True: print("it is true?") else: print("it wasn't true?") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2b945c67ff612ec2"} --> **Exercise 3.4** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-36ba5da4827cc9c8"} def check_number(a): if a <= 5: print(a, "is less than or equal to 5") @@ -683,9 +756,11 @@ check_number(10) check_number(15) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a03f32b890804dc7"} --> **Exercise 3.5** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6ad76460fb6dee8a"} def factorial(a): # f = our factorial result f = 1 @@ -701,9 +776,11 @@ print(factorial(4)) print(factorial(0)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b7807a055420b17b"} --> **Exercise 3.6** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1536f4bee510c9ac"} # Your code here s = 0 @@ -713,12 +790,14 @@ for i in (1,2,3,4,5,6,7,8,9,10): print("Sum is", s) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-361135ad0db80c76"} --> **Exercise 3.7** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4dc583cec91521e9"} s = 0 -# Note than range should end at 101! +# Note the range should end at 101! # It should also start at 1: range(101) will also execute # the loop with i=0 (which won't matter here...) for i in range(1,101): @@ -727,9 +806,11 @@ for i in range(1,101): print(s) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-21f178bba5a2f9ad"} --> **Exercise 3.8** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-188d5c2433fb8aa3"} # Solution maxnum=1e6 @@ -737,8 +818,7 @@ i=0 while i <= maxnum: if i*(i-10)==257024: break - else: - i+=1 - + i+=1 + print(i) ``` diff --git a/Notebook 4/Notebook 4 Scientific Computing with Numpy.ipynb b/Notebook 4/Notebook 4 Scientific Computing with Numpy.ipynb index a0c8d38b5f21862c0a9e04db8e64df00aec4af5f..18ff03222cb1a314c4b41eb5a07be83f2575ab35 100644 --- a/Notebook 4/Notebook 4 Scientific Computing with Numpy.ipynb +++ b/Notebook 4/Notebook 4 Scientific Computing with Numpy.ipynb @@ -2,7 +2,16 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-33372ef05bccfcb0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "# Scientific Computing in Python with Numpy \n", "\n", @@ -23,7 +32,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7d7b639cd03ba916", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Numpy Arrays\n", "\n", @@ -65,8 +83,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4cd897115e9a55da", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -76,7 +102,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2b8a256ccf823e14", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Now that we have imported numpy, we can use functions in numpy to create a numpy array. A simple way to do this is to use the function `np.array()` to make a numpy array from a comma-separated list of numbers in square brackets:" ] @@ -93,14 +128,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-cfdb9edf4b2e1707", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note that numpy does not make a distinction between row vectors and column vectors: there are just vectors. " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-aa801acee39c518f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Indexing arrays (and counting from zero)\n", "\n", @@ -132,7 +185,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-992a7aa5df6186e4", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**WRONG!** Why? Because the makers of Python decided to start counting from zero: the first element of tuple `a` is actually `a[0]`. \n", "\n", @@ -154,7 +216,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ab7bf8fa5a2ac10f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Here the `len` function returns the length of the array `a`. As we saw before, Python has very smart `for` loops that can automatically iterate over many types of objects, which means we can also print out all the elements of our array like this:" ] @@ -171,7 +242,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e1299e2159378fdb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In Python, if you try to index beyond the end of the array, you will get an error: " ] @@ -189,7 +269,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-77aff5e6ccdff810", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "(Remember: indexing starts at zero!)\n", "\n", @@ -220,7 +309,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8398fd216f6b2f58", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "We can also use indexing to change the values of elements in our array:" ] @@ -238,7 +336,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3dae75f8494e44f3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.1** Set the first three, and the last two, entries of the following array to zero:" ] @@ -260,7 +367,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3b080cba952a146f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Slicing numpy arrays\n", "\n", @@ -288,7 +404,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-70abffff9b88cf27", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "The notation `a[0:5]` has \"sliced\" out the first five elements of the array.\n", "\n", @@ -319,7 +444,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a8b2ffcd10cfc7b1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Also handy: you can can have Python slice an array with a \"step\" size that is more than one by adding another `:` and a number after that. Find out its operation using:" ] @@ -337,7 +471,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-24b9d17391ceeb7e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Fun: you can also use negative steps:" ] @@ -355,7 +498,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6ee08109e60cb51f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "And finally, unlike indexing, Python is a bit lenient if you slice off the end of an array:" ] @@ -373,7 +525,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8d1518c5eafa34e8", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.2:** Slicing can also be used to *set* multiple values in an array at the same time. Use slicing to set first 10 entries of the array below to zero in one line of code." ] @@ -388,13 +549,22 @@ "source": [ "a = np.array(range(20))+1\n", "print(a)\n", - "some code that sets the first 20 entries to zero\n", + "some code that sets the first 10 entries to zero\n", "print(a)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9b377accb5c0210c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Mathematical operations on arrays\n", "\n", @@ -464,7 +634,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2963828a5132a24a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "What about if I multiply two vectors together? \n", "\n", @@ -496,7 +675,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-713408533e01c84c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "What if I actually want the dot product or the outer product? For that, Python has functions `np.dot()` and `np.outer()`: " ] @@ -521,7 +709,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d29f4ba63452bfee", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Pretty much all operators work with numpy arrays, even comparison operators, which can sometimes be very handy:" ] @@ -538,7 +735,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8657218bbd2eabbc", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.3:** Generate a sequence of the first 20 powers of 2 in a numpy array (starting at $2^0$). \n", "\n", @@ -555,12 +761,21 @@ }, "outputs": [], "source": [ - "code that makes the desired array" + "# your code that makes the desired array" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-809eedfdadcd8b57", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Functions for creating numpy arrays\n", "\n", @@ -589,7 +804,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a3a728bdca7ae5e3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### `np.linspace`\n", "\n", @@ -617,7 +841,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ea30686a4490a44d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note that if we wanted to have a step size of exactly 0.5, we need a total of 41 points:" ] @@ -637,7 +870,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-60d973103e7a78a9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.4:** Generate an array that runs from -2 to 1 with 20 points using `linspace`." ] @@ -656,7 +898,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7c4a427b963a9b3c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### `np.arange()`\n", "\n", @@ -678,7 +929,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5b97dd88408ae9d0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Here, we already see a small quirk of `arange`: it will stop once the next point it calculates is `<` (not `<=`) to the stop point. If we want to get a range that stops at `20.0`, we need to make the stop point any number a bit bigger than 20 (but smaller than our step size):" ] @@ -698,14 +958,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ab1a61494f1f5b33", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "For this reason, I do not find myself using `np.arange()` very often, and mostly use `np.linspace()`. There are also several other useful functions, such as <a href=https://docs.scipy.org/doc/numpy/reference/generated/numpy.geomspace.html>np.geomspace()</a>, which produces geometrically spaced points (such that they are evenly spaced on a log scale). " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3a0d9465353f954b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.5:** Generate a numpy array that has a first element with value 60 and last element 50 and takes steps of -0.5 between the values. " ] @@ -724,7 +1002,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ed2f97bb11cf695d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Random numbers \n", "\n", @@ -745,18 +1032,38 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-624c748e1b750b24", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "This will generate uniform random numbers on the range of 0 to 1, but there are also several other random number generator functions that can make <a href=https://en.wikipedia.org/wiki/Normal_distribution>normally distributed</a> random numbers, or random integers, and more." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-bf4eeb182ddf3fdf", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.6:** Generate a numpy array that contains 300 random grades that have a distribution of a <a href=https://www.mathsisfun.com/data/standard-normal-distribution.html>bell-shaped curve</a> that might represent the final grades of the students in this course, with an average grade of 7.5 and a standard deviation of 1. Make sure your grades are rounded to a half point.\n", "\n", - "(You may find it useful have to look at the help of the `np.random.normal()` function, and at your answer from Assignment 1 for the rounding.)" + "(You may find it useful have to look at the help of the `np.random.normal()` function, and at your answer from Assignment 1 for the rounding.)\n", + "\n", + "(Because of the properties of a normal distribution a small pecentage of the grades may be above a 10, you may leave this for now.)" ] }, { @@ -774,7 +1081,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-042c8d00bb09795d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Multidimensional arrays (matrices)\n", "\n", @@ -793,7 +1109,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-63e005300e7f7098", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "For two dimensional matrices, the usual function `len()` is not enough to tell us about the shape of our matrix. Instead, we can use a property of the numpy matrix itself called its `shape`:" ] @@ -810,7 +1135,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f8e1398b61d09699", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Indexing two dimensional arrays works by using commas inside square brackets to specify the index of the first and second dimensions:" ] @@ -834,7 +1168,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4e9af994bf9d7df7", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can also use slicing to to assign values to an array from a vector, which can be a handy way to enter a matrix by hand:" ] @@ -854,7 +1197,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-461ca601abc17699", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Similarly, slicing also can be used to extract rows, columns, or blocks of the matrix:" ] @@ -893,7 +1245,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6ab7d9e10575586d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "There are several functions for making matrices which you may find useful someday: \n", "\n", @@ -926,7 +1287,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-484f34f51c8f8c5e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.7:** Use Python to calculate the following matrix multiplication: \n", "\n", @@ -943,7 +1313,7 @@ "\\end{bmatrix}\n", "$$\n", "\n", - "To perofrm this multiplication, you will need to use the `matmul` or `dot` routine of numpy: \n", + "To perform this multiplication, you will need to use the `matmul` or `dot` routine of numpy: \n", "\n", "https://docs.scipy.org/doc/numpy/reference/generated/numpy.matmul.html\n", "https://docs.scipy.org/doc/numpy/reference/generated/numpy.dot.html\n", @@ -980,7 +1350,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-0a8f096130330d1f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Numpy functions \n", "\n", @@ -1008,7 +1387,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-471d12979c1baf8e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Because this is a common operation, there is a function built into numpy `np.average()` that can do this for you:" ] @@ -1024,7 +1412,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-48cd73f9aeaf481e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "This is very handy: it saves us loads of typing! From the function name, it is also easy to understand what you are doing, making the code clearer and easier to read. However, the purpose of numpy functions is not only to save lots of typing: they also can often perform calculations MUCH faster than if you do program the calculation yourself with a `for` loop, as we will see in the next section.\n", "\n", @@ -1066,7 +1463,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3c32880c009136b2", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Good question for you to think about: why is this not zero? And what would I have to change above to get the code to return zero? \n", "\n", @@ -1087,7 +1493,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d520c5750ce4f54a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note also here that we used round brackets `()` around the `a**2` in the `print` statement to be able to then index the resulting array `a**2` array using square brackets `[]`. \n", "\n", @@ -1102,7 +1517,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-081110b083e1eeb8", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.8:** Make a array `x` that runs from 0 to 4 with 20 points, and calculate an array `y` whose entries are equal to the square root of the entries in `x`. " ] @@ -1121,7 +1545,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-dfed25e05b150ef3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### \"Vectorisation\" and fast code with numpy functions\n", "\n", @@ -1175,7 +1608,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-bbb6d85899ba86b1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Why is numpy so much faster? The reason is that Python is an <a href=https://en.wikipedia.org/wiki/Interpreted_language>interpreted language</a>. In each of the steps of the `for` loop, the Python kernel reads in the next step it has to do, translates that into an instruction for your computer processor, asks the computer to perform the step, gets the result back, reads in the next step, translates that into a processor instruction, sends that as an instruction to the computer processor, etc, etc. \n", "\n", @@ -1202,7 +1644,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2a60c21baccb3c7d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ " Here is a nice example of a vectorized way of counting the number of times the number '5' occurs in a random sample of 100 integers between 0 and 20:" ] @@ -1219,7 +1670,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c8bc80849f5e0e0a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "To see how this works, we can look at the intermediate steps:" ] @@ -1238,7 +1698,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1a3f30004c3e3ffb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note that in this case, `np.sum()` will convert the `bool` value `True` into `1` and `False` into `0` for calculating the sum, according the the standard convertion of `bool` types to `int` types. You can see this in action if you want using the function `astype()` that is built into numpy arrays:" ] @@ -1255,14 +1724,81 @@ }, { "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-dd8dc8bd0ab88a1c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "Another neat feature that `numpy` has is that is can 'vectorize' normal python functions so that they can take `numpy` functions and make them a bit faster (10-20%). This is done using the `np.frompyfunc` function. An example is given below." + ] + }, + { + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], + "source": [ + "def fac(N):\n", + " if (N == 1) or (N == 0):\n", + " return 1\n", + " return N * fac(N-1)\n", + "\n", + "#As you can see applying the following array does not work\n", + "N = np.arange(9)\n", + "\n", + "try:\n", + " print(fac(N))\n", + " print(\"This works\")\n", + "except:\n", + " print(\"This does not work \\n\")\n", + " \n", + "#Now if we make this a numpy function is will work \n", + "#(the numbers in the frompyfunc represent the input and output values)\n", + "\n", + "numpy_fac = np.frompyfunc(fac,1,1)\n", + "\n", + "\n", + "try:\n", + " print(numpy_fac(N))\n", + " print(\"This works\")\n", + "except:\n", + " print(\"This does not work\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2f8479d8cc410928", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Solutions to exercises" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-0a4e3eec30674d19", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.1** " ] @@ -1271,6 +1807,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-daf14d85fa463aba", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1293,7 +1837,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1f286c69f57cbc80", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.2:** " ] @@ -1302,6 +1855,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-efb5e7292fc18003", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1314,7 +1875,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b47cf723add61750", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.3:** " ] @@ -1323,6 +1893,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-87b5343c8e0fdbce", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1334,7 +1912,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ea7ddbad81cf4ad1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.4:** " ] @@ -1343,6 +1930,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e6d951d0073dc45f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1353,7 +1948,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d33f7386d70b8e4f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.5:** " ] @@ -1362,6 +1966,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-84bb816a29f6f1bb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1372,7 +1984,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-095bdb4fd5d1fae4", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.6:** " ] @@ -1381,6 +2002,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5a51a5dde00116ba", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": false }, "outputs": [], @@ -1392,7 +2021,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f8253b40b7bb046e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.7:** " ] @@ -1401,6 +2039,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a69f7838d0b9e25a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": false }, "outputs": [], @@ -1435,7 +2081,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3ba058efb16a91ad", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.8:** " ] @@ -1444,6 +2099,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-57c551e2a58429d8", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1455,6 +2118,7 @@ } ], "metadata": { + "celltoolbar": "Create Assignment", "jupytext": { "formats": "ipynb,md" }, @@ -1475,6 +2139,24 @@ "pygments_lexer": "ipython3", "version": "3.7.5" }, + "latex_envs": { + "LaTeX_envs_menu_present": true, + "autoclose": false, + "autocomplete": true, + "bibliofile": "biblio.bib", + "cite_by": "apalike", + "current_citInitial": 1, + "eqLabelWithNumbers": true, + "eqNumInitial": 1, + "hotkeys": { + "equation": "Ctrl-E", + "itemize": "Ctrl-I" + }, + "labels_anchors": false, + "latex_user_defs": false, + "report_style_numbering": false, + "user_envs_cfg": false + }, "toc": { "base_numbering": "4", "nav_menu": {}, diff --git a/Notebook 4/Notebook 4 Scientific Computing with Numpy.md b/Notebook 4/Notebook 4 Scientific Computing with Numpy.md index c0bbdd8b2893ec332f4ae3c9f70b9bf5396d453f..5fc800156f1b231fe27f007c1344ba3ce96534aa 100644 --- a/Notebook 4/Notebook 4 Scientific Computing with Numpy.md +++ b/Notebook 4/Notebook 4 Scientific Computing with Numpy.md @@ -13,6 +13,7 @@ jupyter: name: python3 --- +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-33372ef05bccfcb0"} --> # Scientific Computing in Python with Numpy <a href=https://numpy.org/>Numpy</a> (numerical Python) is a library designed for performing scientific computing in Python. @@ -28,8 +29,9 @@ In this notebook, we will introduce numpy arrays, a data structure introduced in * Student is able to use functions for creating arrays (eg. `np.zeros()`, `np.linspace()`, `np.random.random()` * Student is able to use numpy functions for vectorized calculations * Student is able to demonstrate the speed increase of vectorized calculations using `time()` +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7d7b639cd03ba916"} --> ## Numpy Arrays Until now, the variable types we have been working with in Python represent relatively simple data types: @@ -66,21 +68,26 @@ $$ In linear algebra you are used to manipulate these vectors, this can be done in a similar way with numpy arrays. We will use numpy arrays extensively in Python as vectors, like above, but also for storing, manipulating, and analyzing datasets (like a column of an excel spreadsheet). To use numpy arrays, we first need to import the numpy library, which we will do using the shortened name "np" (to save typing): +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4cd897115e9a55da"} import numpy as np ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2b8a256ccf823e14"} --> Now that we have imported numpy, we can use functions in numpy to create a numpy array. A simple way to do this is to use the function `np.array()` to make a numpy array from a comma-separated list of numbers in square brackets: +<!-- #endregion --> ```python a = np.array([1,2,3,4,5]) print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-cfdb9edf4b2e1707"} --> Note that numpy does not make a distinction between row vectors and column vectors: there are just vectors. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-aa801acee39c518f"} --> ### Indexing arrays (and counting from zero) One useful thing about arrays is that you can access the elements of the array using square brackets: @@ -98,16 +105,19 @@ a = np.array([1,2,3,4,5]) The first element of `a` is `1`. You might think that if you want to access the first element of `a`, you would use the notation `a[1]`. Right? Let's try it: +<!-- #endregion --> ```python print(a[1]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-992a7aa5df6186e4"} --> **WRONG!** Why? Because the makers of Python decided to start counting from zero: the first element of tuple `a` is actually `a[0]`. (This is a long-standing <a href=https://en.wikipedia.org/wiki/Zero-based_numbering>discussion among computer scientists</a>, and the convention is <a href=https://en.wikipedia.org/wiki/Comparison_of_programming_languages_(array)#Array_dimensions>different</a> in many different languages. There are advantages and disadvantages of both, and even essays written about it...but in any case, Python chose to start arrays at zero.) This also helps better understand the `range()` function: for example, to loop over all the elements in `a`, I can use this code: +<!-- #endregion --> ```python for i in range(len(a)): @@ -115,22 +125,28 @@ for i in range(len(a)): print('a[%d] is %d' % (i,n)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ab7bf8fa5a2ac10f"} --> Here the `len` function returns the length of the array `a`. As we saw before, Python has very smart `for` loops that can automatically iterate over many types of objects, which means we can also print out all the elements of our array like this: +<!-- #endregion --> ```python for n in a: print(n) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-e1299e2159378fdb"} --> In Python, if you try to index beyond the end of the array, you will get an error: +<!-- #endregion --> ```python a[5] ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-77aff5e6ccdff810"} --> (Remember: indexing starts at zero!) Python also has a handy feature: negative indices count backwards from the end, and index `-1` corresponds to the last element in the array! +<!-- #endregion --> ```python a[-1] @@ -140,7 +156,9 @@ a[-1] a[-2] ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8398fd216f6b2f58"} --> We can also use indexing to change the values of elements in our array: +<!-- #endregion --> ```python print(a) @@ -148,7 +166,9 @@ a[2] = -1 print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3dae75f8494e44f3"} --> **Exercise 4.1** Set the first three, and the last two, entries of the following array to zero: +<!-- #endregion --> ```python a = np.array([1,2,3,4,5,6,7,8,9,10,11,32,55,78,22,99,55,33.2,55.77,99,101.3]) @@ -158,6 +178,7 @@ a = np.array([1,2,3,4,5,6,7,8,9,10,11,32,55,78,22,99,55,33.2,55.77,99,101.3]) print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3b080cba952a146f"} --> ### Slicing numpy arrays Numpy arrays also support a special type of indexing called "slicing" that does not just return a single element of an array, but instead returns a whole part of array. @@ -167,6 +188,7 @@ To do this, I put not just a single number inside my square brackets, but instea `a[n:m]` will return a new tuple that consist of all the elements in `a`, starting at element `n` and ending at element `m-1`. Let's look at a concrete example: +<!-- #endregion --> ```python a = np.array(range(10)) @@ -174,9 +196,11 @@ print(a) print(a[0:5]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-70abffff9b88cf27"} --> The notation `a[0:5]` has "sliced" out the first five elements of the array. With slicing, you can also leave off either `n` or `m` from the slice: if leave off `n` it will default to `n=0`, and if you leave off `m`, it will default to the end of the array (also the same as `m=-1` in Python indexing): +<!-- #endregion --> ```python print(a[:5]) @@ -186,36 +210,46 @@ print(a[:5]) print(a[5:]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a8b2ffcd10cfc7b1"} --> Also handy: you can can have Python slice an array with a "step" size that is more than one by adding another `:` and a number after that. Find out its operation using: +<!-- #endregion --> ```python print(a[0:10:2]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-24b9d17391ceeb7e"} --> Fun: you can also use negative steps: +<!-- #endregion --> ```python print(a[-1:-11:-1]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6ee08109e60cb51f"} --> And finally, unlike indexing, Python is a bit lenient if you slice off the end of an array: +<!-- #endregion --> ```python print(a[0:20]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8d1518c5eafa34e8"} --> **Exercise 4.2:** Slicing can also be used to *set* multiple values in an array at the same time. Use slicing to set first 10 entries of the array below to zero in one line of code. +<!-- #endregion --> ```python a = np.array(range(20))+1 print(a) -some code that sets the first 20 entries to zero +some code that sets the first 10 entries to zero print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-9b377accb5c0210c"} --> ### Mathematical operations on arrays An advantage of using numpy arrays for scientific computing is that way they behave under mathematical operations. In particular, they very often do exactly what we would want them to do if they were a vector: +<!-- #endregion --> ```python a = np.array([1,2,3,4,5]) @@ -242,6 +276,7 @@ print(a/2) print(a**2) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2963828a5132a24a"} --> What about if I multiply two vectors together? In mathematics, if I multiply two vectors, what I get depends on if I use the "dot product" or the "outer product" for my multiplication: @@ -259,12 +294,15 @@ It turns out: it uses **neither!** In Python, the notation `a*a` produces what i (Mathematically, this has a fancy name called the <a href=https://en.wikipedia.org/wiki/Hadamard_product_(matrices)>Hadamard product</a>, but as you can see, despite the fancy name, it's actually very simple...) We can see this in action here: +<!-- #endregion --> ```python print(a*a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-713408533e01c84c"} --> What if I actually want the dot product or the outer product? For that, Python has functions `np.dot()` and `np.outer()`: +<!-- #endregion --> ```python print(np.dot(a,a)) @@ -274,26 +312,32 @@ print(np.dot(a,a)) print(np.outer(a,a)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d29f4ba63452bfee"} --> Pretty much all operators work with numpy arrays, even comparison operators, which can sometimes be very handy: +<!-- #endregion --> ```python print(a) print(a>3) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8657218bbd2eabbc"} --> **Exercise 4.3:** Generate a sequence of the first 20 powers of 2 in a numpy array (starting at $2^0$). Your output should be an array $[2^0, 2^1, 2^2, 2^3, ...]$. *(Hint: Start with a numpy array created using an appropriate range function that makes an array [0,1,2,3,...])* +<!-- #endregion --> ```python -code that makes the desired array +# your code that makes the desired array ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-809eedfdadcd8b57"} --> ## Functions for creating numpy arrays In numpy, there are also several handy functions for automatically creating arrays: +<!-- #endregion --> ```python a = np.zeros(10) @@ -305,6 +349,7 @@ a = np.ones(10) print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a3a728bdca7ae5e3"} --> ### `np.linspace` To automatically generate an array with linerly increasing values you can take `np.linspace()`: @@ -314,6 +359,7 @@ https://docs.scipy.org/doc/numpy/reference/generated/numpy.linspace.html It takes three arguments: the starting number, the ending number, and the number of points. This is a bit like the `range` function we saw before, but allows you to pick the total number of points, automatically calculating the (non-integer) step size you need: +<!-- #endregion --> ```python a = np.linspace(0,20,40) @@ -323,7 +369,9 @@ print("Length is: ", len(a)) print("Step size is: ", a[1]-a[0]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ea30686a4490a44d"} --> Note that if we wanted to have a step size of exactly 0.5, we need a total of 41 points: +<!-- #endregion --> ```python a = np.linspace(0,20,41) @@ -333,16 +381,20 @@ print("Length is: ", len(a)) print("Step size is: ", a[1]-a[0]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-60d973103e7a78a9"} --> **Exercise 4.4:** Generate an array that runs from -2 to 1 with 20 points using `linspace`. +<!-- #endregion --> ```python a = your code print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7c4a427b963a9b3c"} --> ### `np.arange()` If we want to have more control on the exact spacing, we can use the `np.arange()` function. It is like `range()`, asking you for the start, stop, and step size: +<!-- #endregion --> ```python a = np.arange(0,20,0.5) @@ -352,7 +404,9 @@ print("Length is: ", len(a)) print("Step size is: ", a[1]-a[0]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5b97dd88408ae9d0"} --> Here, we already see a small quirk of `arange`: it will stop once the next point it calculates is `<` (not `<=`) to the stop point. If we want to get a range that stops at `20.0`, we need to make the stop point any number a bit bigger than 20 (but smaller than our step size): +<!-- #endregion --> ```python a = np.arange(0,20.00000001,0.5) @@ -362,55 +416,71 @@ print("Length is: ", len(a)) print("Step size is: ", a[1]-a[0]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ab1a61494f1f5b33"} --> For this reason, I do not find myself using `np.arange()` very often, and mostly use `np.linspace()`. There are also several other useful functions, such as <a href=https://docs.scipy.org/doc/numpy/reference/generated/numpy.geomspace.html>np.geomspace()</a>, which produces geometrically spaced points (such that they are evenly spaced on a log scale). +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3a0d9465353f954b"} --> **Exercise 4.5:** Generate a numpy array that has a first element with value 60 and last element 50 and takes steps of -0.5 between the values. +<!-- #endregion --> ```python a = your code print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ed2f97bb11cf695d"} --> ### Random numbers Numpy can also generate arrays of random numbers: +<!-- #endregion --> ```python a = np.random.random(40) print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-624c748e1b750b24"} --> This will generate uniform random numbers on the range of 0 to 1, but there are also several other random number generator functions that can make <a href=https://en.wikipedia.org/wiki/Normal_distribution>normally distributed</a> random numbers, or random integers, and more. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-bf4eeb182ddf3fdf"} --> **Exercise 4.6:** Generate a numpy array that contains 300 random grades that have a distribution of a <a href=https://www.mathsisfun.com/data/standard-normal-distribution.html>bell-shaped curve</a> that might represent the final grades of the students in this course, with an average grade of 7.5 and a standard deviation of 1. Make sure your grades are rounded to a half point. (You may find it useful have to look at the help of the `np.random.normal()` function, and at your answer from Assignment 1 for the rounding.) +(Because of the properties of a normal distribution a small pecentage of the grades may be above a 10, you may leave this for now.) +<!-- #endregion --> + ```python #Your code here that results in a numpy array rounded_grades ...some code... print(rounded_grades) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-042c8d00bb09795d"} --> ## Multidimensional arrays (matrices) Although we will not use them too much in this course, we can also use numpy also supports two-dimensional (or N-dimensional) numpy arrays, that can represent matrices. To make a 2D numpy array, you can use the `zeros()` function, for example, but with a two-entry list of numbers specifying the size N and M of the matrix: +<!-- #endregion --> ```python m = np.zeros([10,10]) print(m) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-63e005300e7f7098"} --> For two dimensional matrices, the usual function `len()` is not enough to tell us about the shape of our matrix. Instead, we can use a property of the numpy matrix itself called its `shape`: +<!-- #endregion --> ```python print(len(m)) print(m.shape) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f8e1398b61d09699"} --> Indexing two dimensional arrays works by using commas inside square brackets to specify the index of the first and second dimensions: +<!-- #endregion --> ```python a = np.array(range(1,6)) @@ -424,7 +494,9 @@ print("\nAfter changing entry [2,3] to -1:") print(m) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4e9af994bf9d7df7"} --> You can also use slicing to to assign values to an array from a vector, which can be a handy way to enter a matrix by hand: +<!-- #endregion --> ```python m = np.zeros([3,3]) @@ -434,7 +506,9 @@ m[2,:] = [7,8,9] print(m) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-461ca601abc17699"} --> Similarly, slicing also can be used to extract rows, columns, or blocks of the matrix: +<!-- #endregion --> ```python # A row @@ -451,11 +525,13 @@ print(m[:,1]) print(m[1:,1:]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6ab7d9e10575586d"} --> There are several functions for making matrices which you may find useful someday: https://docs.scipy.org/doc/numpy/reference/routines.array-creation.html including this one which is used often: +<!-- #endregion --> ```python # The identity matrix @@ -467,6 +543,7 @@ print(np.eye(10)) print(np.eye(10,k=-1)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-484f34f51c8f8c5e"} --> **Exercise 4.7:** Use Python to calculate the following matrix multiplication: $$ @@ -482,7 +559,7 @@ $$ \end{bmatrix} $$ -To perofrm this multiplication, you will need to use the `matmul` or `dot` routine of numpy: +To perform this multiplication, you will need to use the `matmul` or `dot` routine of numpy: https://docs.scipy.org/doc/numpy/reference/generated/numpy.matmul.html https://docs.scipy.org/doc/numpy/reference/generated/numpy.dot.html @@ -490,6 +567,7 @@ https://docs.scipy.org/doc/numpy/reference/generated/numpy.dot.html *(For the nerds: do these two matrices <a href=https://en.wikipedia.org/wiki/Commuting_matrices>commute?</a>)* *(For the real nerds: have your program check if they commute and inform the user!)* +<!-- #endregion --> ```python m1 = np.zeros([3,3]) @@ -509,11 +587,13 @@ print(product) do nerd stuff if you want... ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-0a8f096130330d1f"} --> ## Numpy functions We can use `for` loops to iterate through numpy arrays and perform calculations. For example, this code will calculate the average value of all the numbers in an array: +<!-- #endregion --> ```python a = np.linspace(1,20,20) @@ -527,15 +607,19 @@ avg /= len(a) print("Average is", avg) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-471d12979c1baf8e"} --> Because this is a common operation, there is a function built into numpy `np.average()` that can do this for you: +<!-- #endregion --> ```python print("Average is", np.average(a)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-48cd73f9aeaf481e"} --> This is very handy: it saves us loads of typing! From the function name, it is also easy to understand what you are doing, making the code clearer and easier to read. However, the purpose of numpy functions is not only to save lots of typing: they also can often perform calculations MUCH faster than if you do program the calculation yourself with a `for` loop, as we will see in the next section. Python also has many other useful functions for performing calculations using arrays: +<!-- #endregion --> ```python # Calculate the standard deviation @@ -553,9 +637,11 @@ a = np.linspace(-10,10,100) print(np.min(a**2)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3c32880c009136b2"} --> Good question for you to think about: why is this not zero? And what would I have to change above to get the code to return zero? In addition to finding the minimum value in a vector, the function `argmin` can tell you **where** (what index number) the minimum is: +<!-- #endregion --> ```python # Find the index number of the minimum of the array @@ -564,6 +650,7 @@ print(i) print((a**2)[i]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d520c5750ce4f54a"} --> Note also here that we used round brackets `()` around the `a**2` in the `print` statement to be able to then index the resulting array `a**2` array using square brackets `[]`. You can find the full list of mathematical numpy functions on the documentation website: @@ -573,15 +660,18 @@ https://docs.scipy.org/doc/numpy/reference/routines.math.html and the full list of all functions in the reference guide: https://docs.scipy.org/doc/numpy/reference/index.html +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-081110b083e1eeb8"} --> **Exercise 4.8:** Make a array `x` that runs from 0 to 4 with 20 points, and calculate an array `y` whose entries are equal to the square root of the entries in `x`. +<!-- #endregion --> ```python your code to make the requested arrays x and y print(y) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-dfed25e05b150ef3"} --> ### "Vectorisation" and fast code with numpy functions In the first example above, we showed two ways of calculating an average: one using a `for` loop, and one using the numpy function. @@ -591,6 +681,7 @@ Functionally, they are equivalent: they do exactly the same thing. A curious feature of Python is that if you use functions instead of coding loops yourself, often things are **MUCH MUCH** faster. To show this quantitatively, we will use the `time` library to calculate the time it takes to find the average of a pretty big array using both techniques: +<!-- #endregion --> ```python # The time() function from the time library will return a floating point number representing @@ -626,6 +717,7 @@ print("Numpy took %.3f seconds" % (t2-t1)) print("\nNumpy was %.1f times faster!" % (t_forloop/t_np)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-bbb6d85899ba86b1"} --> Why is numpy so much faster? The reason is that Python is an <a href=https://en.wikipedia.org/wiki/Interpreted_language>interpreted language</a>. In each of the steps of the `for` loop, the Python kernel reads in the next step it has to do, translates that into an instruction for your computer processor, asks the computer to perform the step, gets the result back, reads in the next step, translates that into a processor instruction, sends that as an instruction to the computer processor, etc, etc. If we did the same test in a <a href=https://en.wikipedia.org/wiki/Compiled_language>compiled programing language</a> like <a href=https://en.wikipedia.org/wiki/C_(programming_language)>C</a>, there would be no difference if we used a library function or if we wrote our own `for` loop. @@ -637,20 +729,25 @@ When you use smart functions in Python libraries, like (many of) those in numpy, In the language of interpreted programmers, finding smart ways of getting what you need done using "compiled library functions" is often referred to as <a href=https://en.wikipedia.org/wiki/Array_programming>vectorisation</a>. Note that even normal mathematical operators are actually "vectorized functions" when they operate: +<!-- #endregion --> ```python # This is actually a vectorized 'for' loop, it involves multiplying 50 million numbers by 5 b = 5*a ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2a60c21baccb3c7d"} --> Here is a nice example of a vectorized way of counting the number of times the number '5' occurs in a random sample of 100 integers between 0 and 20: +<!-- #endregion --> ```python nums = np.random.randint(0,21,100) print("There are %d fives" % np.sum(nums == 5)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c8bc80849f5e0e0a"} --> To see how this works, we can look at the intermediate steps: +<!-- #endregion --> ```python nums = np.random.randint(0,21,100) @@ -659,19 +756,56 @@ print(nums == 5) print("There are %d fives" % np.sum(nums == 5)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1a3f30004c3e3ffb"} --> Note that in this case, `np.sum()` will convert the `bool` value `True` into `1` and `False` into `0` for calculating the sum, according the the standard convertion of `bool` types to `int` types. You can see this in action if you want using the function `astype()` that is built into numpy arrays: +<!-- #endregion --> ```python print(nums == 5) print((nums == 5).astype('int')) ``` -## Solutions to exercises +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-dd8dc8bd0ab88a1c"} --> +Another neat feature that `numpy` has is that is can 'vectorize' normal python functions so that they can take `numpy` functions and make them a bit faster (10-20%). This is done using the `np.frompyfunc` function. An example is given below. +<!-- #endregion --> + +```python +def fac(N): + if (N == 1) or (N == 0): + return 1 + return N * fac(N-1) + +#As you can see applying the following array does not work +N = np.arange(9) + +try: + print(fac(N)) + print("This works") +except: + print("This does not work \n") + +#Now if we make this a numpy function is will work +#(the numbers in the frompyfunc represent the input and output values) +numpy_fac = np.frompyfunc(fac,1,1) + +try: + print(numpy_fac(N)) + print("This works") +except: + print("This does not work") +``` + +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2f8479d8cc410928"} --> +## Solutions to exercises +<!-- #endregion --> + +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-0a4e3eec30674d19"} --> **Exercise 4.1** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-daf14d85fa463aba"} a = np.array([1,2,3,4,5,6,7,8,9,10,11,32,55,78,22,99,55,33.2,55.77,99,101.3]) #some code to set the first three and last two entries to zero @@ -688,48 +822,60 @@ a[-1] = 0 print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1f286c69f57cbc80"} --> **Exercise 4.2:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-efb5e7292fc18003"} a = np.array(range(20))+1 print(a) a[0:10] = 0 print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b47cf723add61750"} --> **Exercise 4.3:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-87b5343c8e0fdbce"} n = np.array(range(21)) out = 2**n print(out) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ea7ddbad81cf4ad1"} --> **Exercise 4.4:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-e6d951d0073dc45f"} a = np.linspace(-2,1,20) print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d33f7386d70b8e4f"} --> **Exercise 4.5:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-84bb816a29f6f1bb"} a = np.arange(60,49.9,-0.5) print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-095bdb4fd5d1fae4"} --> **Exercise 4.6:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5a51a5dde00116ba"} raw = np.random.normal(7.5,1,300) rounded_grades = np.round(raw*2)/2 print(rounded_grades) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f8253b40b7bb046e"} --> **Exercise 4.7:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a69f7838d0b9e25a"} m1 = np.zeros([3,3]) m1[:,0] = [1,1,0] m1[:,1] = [0,2,1] @@ -758,9 +904,11 @@ else: print("they don't commute") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3ba058efb16a91ad"} --> **Exercise 4.8:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-57c551e2a58429d8"} x = np.linspace(0,4,20) y = np.sqrt(x) print(y) diff --git a/Notebook 5/Notebook 5 Data in Python.ipynb b/Notebook 5/Notebook 5 Data in Python.ipynb index 1ff0075e57624149a2f3b5b4d8cf6a2b4e9c88c5..e4e8ca5f891ffbff48070387240eef164e982bc4 100644 --- a/Notebook 5/Notebook 5 Data in Python.ipynb +++ b/Notebook 5/Notebook 5 Data in Python.ipynb @@ -2,7 +2,16 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e43ee4f9ec42e73b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "# Data in Python: Loading, Plotting, and Fitting\n", "\n", @@ -11,7 +20,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-def642c582364cae", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Learning objectives for this notebook:**\n", "\n", @@ -28,7 +46,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f79c995cb536082e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Loading and saving data with Numpy\n", "\n", @@ -77,22 +104,36 @@ "\n", "It can handle any type of delimiter: the default is any whitespace (tab or space), but this can also be configured using the `delimiter=` keyword argument. It also does not care about the file extension: for example, it is fine if a CSV file does not have a `.csv` extension (it doesn't even have to have an extension!). A typical convention is to name the files containing ASCII text data with an extension `.dat`.\n", "\n", - "Let's give it a try using the file `v_vs_time.dat`: " + "Let's give it a try using the file `v_vs_time.dat` in the folder `resource/asnlib/public/`: " ] }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2020-08-21T15:09:52.031601Z", + "start_time": "2020-08-21T15:09:51.560698Z" + } + }, "outputs": [], "source": [ "import numpy as np \n", - "data = np.loadtxt(\"v_vs_time.dat\")" + "data = np.loadtxt(\"resource/asnlib/public/v_vs_time.dat\")" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3aa9fb298e9a108a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Here we have assigned the return value of `np.loadtxt` to a variable `data`, which is a numpy array. But what exactly is our variable `data`? We can find out more by looking at the shape of the returned `data` array:" ] @@ -119,7 +160,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5bace50c3249b281", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "When `np.loadtxt()` loads a file, it will return a 2D numpy array with of shape `(n,m)`, where `n` is the number lines in the file and `m` is the number of columns (here, we have 1000 points and 2 columns). \n", "\n", @@ -138,7 +188,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c63f7111a8fd6835", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "We can look at the first 10 points and see that we have successfully loaded the data from the file! " ] @@ -155,16 +214,34 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9a33fae5f634b3cb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "We can check that the data is correct by opening the data file itself from the Jupyter file browser: Jupyter can also directly load and edit text files. (Another way of loading hand-recorded data into Python is just to use Jupyter to create an ASCII `.dat` file yourself.)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-def50c91314bd96b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ - "**Exercise 5.1:** Load the data from the file `exercise_data.dat`. The first column represents a measurement time in seconds, and the second represents a measurement voltage in volts. How many points are in the dataset? \n", + "**Exercise 5.1:** Load the data from the file `resource/asnlib/public/exercise_data.dat`. The first column represents a measurement time in seconds, and the second represents a measurement voltage in volts. How many points are in the dataset? \n", "\n", "*(Important: make sure that you use different variable names than t and v for the data you load, since we will continue to use the data we loaded in the example code in the sections below!)*" ] @@ -183,7 +260,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-0793eb4c8f85801b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Saving data\n", "\n", @@ -203,7 +289,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-fb66afd2a19403c5", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "To \"pack\" them together into a matrix like the one returned by `np.loadtxt()`, we can first create a 2D matrix of the correct size and then use slicing with an assignment operator `=` to give the columns the correct values:" ] @@ -224,9 +319,18 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7823cff287a7691f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ - "Now we are ready to use `np.savetxt()`:" + "Now we are ready to use `np.savetxt()` in the current folder: " ] }, { @@ -240,7 +344,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5c513d113d2f3e3f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "source": [ + "If you go back to your workspace in a new tab, you will can see that this file has been created, and if you click on it, you can see what is inside it. " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9fc0eeb9a99e75d9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Plotting data and functions with Matplotlib\n", "\n", @@ -255,7 +384,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-24bdaef0687142bb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Plotting basics\n", "\n", @@ -268,7 +406,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -277,7 +415,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d879f0b0f2d9a70f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "I find it handy to also increase the \"dpi\" setting to make the plots a bit bigger. To do this, you need to execute the following in a separate cell from the `import` statement above:" ] @@ -293,7 +440,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-0373ada799aeca8e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "The routine for making line plots of your data is `plt.plot()`: \n", "\n", @@ -314,7 +470,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2dad246fc48c5b7d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Wow, looks really cool! \n", "\n", @@ -348,7 +513,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-67a673593aba72e2", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "If you want, you can also add a grid (depends on your personal taste preference and what you are using the plots for): " ] @@ -368,7 +542,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-58b15e7163e2da55", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can also add vertical or horizontal dashed lines, which I will use here to add zero axis lines:" ] @@ -389,7 +572,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-526802bd953d3531", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Ok, this is all great, but what if I want to plot the voltage vs. time, and not point number? For this, I give `plt.plot()` two arguments:" ] @@ -410,7 +602,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2f9e48b6a1d65039", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Great! This is nearly looking like something I could put into my lab report. But wait: how do I do this? \n", "\n", @@ -432,7 +633,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ed9b147002abbbb6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "If you look in the file browser, you will now see a file `myplot.pdf`. If you open it, you will see that is a high-quality <a href=https://en.wikipedia.org/wiki/Vector_graphics>vector graphics</a> PDF. \n", "\n", @@ -453,7 +663,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6a6c5a44f576d0c0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Do you want a different point size? Or a different symbol? You can see how to change all of this by looking at the documentation page of the plot function: \n", "\n", @@ -482,7 +701,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1d9eda6969d17554", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Matplotlib will automatically change the color of the second dataset (you can also control them manually, see the <a href= https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.plot.html>documentation</a>). But it can be hard to tell which one is which, and for this reason, it can useful to add a legend to your plot, which can be done using this code: " ] @@ -503,7 +731,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a4e6050c6111cb50", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Finally, if you want a really big figure, you can also adjust the figure size:" ] @@ -527,7 +764,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1f4975d1b1b7c3a2", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.2:** Make a plot of the data you loaded from example 5.1. " ] @@ -545,7 +791,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-93d67850489cb7df", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.3:** As you may have noticed, the data from `example_data.dat` looks like a stright line, while the data from `exercise_data.dat` does not seem like straight line, but instead looks like a power law function $V = at^p$ with power $p$. \n", "\n", @@ -565,7 +820,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-78004ad6fb3f74dd", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Active zooming: The \"notebook\" driver\n", "\n", @@ -576,815 +840,32 @@ }, { "cell_type": "code", - "execution_count": 3, - "metadata": {}, + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2020-08-21T15:12:37.167777Z", + "start_time": "2020-08-21T15:12:31.755183Z" + } + }, "outputs": [], "source": [ "# unpack=True is also a handy way to load data \n", "# (saves some extra code)\n", "# Note this is a big data file, it will take a while to load.\n", "\n", - "x,y = np.loadtxt(\"example2.dat\", unpack=True)" + "x,y = np.loadtxt(\"resource/asnlib/public/example2.dat\", unpack=True)" ] }, { "cell_type": "code", - "execution_count": 6, - "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 = $('<div/>');\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", - " fig.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", - " '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n", - " 'ui-helper-clearfix\"/>');\n", - " var titletext = $(\n", - " '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n", - " 'text-align: center; padding: 3px;\"/>');\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 = $('<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 = $('<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 = $('<canvas/>');\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 = $('<div/>');\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 = $('<button/>');\n", - " button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n", - " 'ui-button-icon-only');\n", - " button.attr('role', 'button');\n", - " button.attr('aria-disabled', 'false');\n", - " button.click(method_name, toolbar_event);\n", - " button.mouseover(tooltip, toolbar_mouse_event);\n", - "\n", - " var icon_img = $('<span/>');\n", - " icon_img.addClass('ui-button-icon-primary ui-icon');\n", - " icon_img.addClass(image);\n", - " icon_img.addClass('ui-corner-all');\n", - "\n", - " var tooltip_span = $('<span/>');\n", - " tooltip_span.addClass('ui-button-text');\n", - " tooltip_span.html(tooltip);\n", - "\n", - " button.append(icon_img);\n", - " button.append(tooltip_span);\n", - "\n", - " nav_element.append(button);\n", - " }\n", - "\n", - " var fmt_picker_span = $('<span/>');\n", - "\n", - " var fmt_picker = $('<select/>');\n", - " fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n", - " fmt_picker_span.append(fmt_picker);\n", - " nav_element.append(fmt_picker_span);\n", - " this.format_dropdown = fmt_picker[0];\n", - "\n", - " for (var ind in mpl.extensions) {\n", - " var fmt = mpl.extensions[ind];\n", - " var option = $(\n", - " '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n", - " fmt_picker.append(option);\n", - " }\n", - "\n", - " // Add hover states to the ui-buttons\n", - " $( \".ui-button\" ).hover(\n", - " function() { $(this).addClass(\"ui-state-hover\");},\n", - " function() { $(this).removeClass(\"ui-state-hover\");}\n", - " );\n", - "\n", - " var status_bar = $('<span class=\"mpl-message\"/>');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "}\n", - "\n", - "mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n", - " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", - " // which will in turn request a refresh of the image.\n", - " this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n", - "}\n", - "\n", - "mpl.figure.prototype.send_message = function(type, properties) {\n", - " properties['type'] = type;\n", - " properties['figure_id'] = this.id;\n", - " this.ws.send(JSON.stringify(properties));\n", - "}\n", - "\n", - "mpl.figure.prototype.send_draw_message = function() {\n", - " if (!this.waiting) {\n", - " this.waiting = true;\n", - " this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n", - " }\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype.handle_save = function(fig, msg) {\n", - " var format_dropdown = fig.format_dropdown;\n", - " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", - " fig.ondownload(fig, format);\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype.handle_resize = function(fig, msg) {\n", - " var size = msg['size'];\n", - " if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n", - " fig._resize_canvas(size[0], size[1]);\n", - " fig.send_message(\"refresh\", {});\n", - " };\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n", - " var x0 = msg['x0'] / mpl.ratio;\n", - " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", - " var x1 = msg['x1'] / mpl.ratio;\n", - " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", - " x0 = Math.floor(x0) + 0.5;\n", - " y0 = Math.floor(y0) + 0.5;\n", - " x1 = Math.floor(x1) + 0.5;\n", - " y1 = Math.floor(y1) + 0.5;\n", - " var min_x = Math.min(x0, x1);\n", - " var min_y = Math.min(y0, y1);\n", - " var width = Math.abs(x1 - x0);\n", - " var height = Math.abs(y1 - y0);\n", - "\n", - " fig.rubberband_context.clearRect(\n", - " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n", - "\n", - " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n", - " // Updates the figure title.\n", - " fig.header.textContent = msg['label'];\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_cursor = function(fig, msg) {\n", - " var cursor = msg['cursor'];\n", - " switch(cursor)\n", - " {\n", - " case 0:\n", - " cursor = 'pointer';\n", - " break;\n", - " case 1:\n", - " cursor = 'default';\n", - " break;\n", - " case 2:\n", - " cursor = 'crosshair';\n", - " break;\n", - " case 3:\n", - " cursor = 'move';\n", - " break;\n", - " }\n", - " fig.rubberband_canvas.style.cursor = cursor;\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_message = function(fig, msg) {\n", - " fig.message.textContent = msg['message'];\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_draw = function(fig, msg) {\n", - " // Request the server to send over a new figure.\n", - " fig.send_draw_message();\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n", - " fig.image_mode = msg['mode'];\n", - "}\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function() {\n", - " // Called whenever the canvas gets updated.\n", - " this.send_message(\"ack\", {});\n", - "}\n", - "\n", - "// A function to construct a web socket function for onmessage handling.\n", - "// Called in the figure constructor.\n", - "mpl.figure.prototype._make_on_message_function = function(fig) {\n", - " return function socket_on_message(evt) {\n", - " if (evt.data instanceof Blob) {\n", - " /* FIXME: We get \"Resource interpreted as Image but\n", - " * transferred with MIME type text/plain:\" errors on\n", - " * Chrome. But how to set the MIME type? It doesn't seem\n", - " * to be part of the websocket stream */\n", - " evt.data.type = \"image/png\";\n", - "\n", - " /* Free the memory for the previous frames */\n", - " if (fig.imageObj.src) {\n", - " (window.URL || window.webkitURL).revokeObjectURL(\n", - " fig.imageObj.src);\n", - " }\n", - "\n", - " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", - " evt.data);\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", - " fig.imageObj.src = evt.data;\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - "\n", - " var msg = JSON.parse(evt.data);\n", - " var msg_type = msg['type'];\n", - "\n", - " // Call the \"handle_{type}\" callback, which takes\n", - " // the figure and JSON message as its only arguments.\n", - " try {\n", - " var callback = fig[\"handle_\" + msg_type];\n", - " } catch (e) {\n", - " console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n", - " return;\n", - " }\n", - "\n", - " if (callback) {\n", - " try {\n", - " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", - " callback(fig, msg);\n", - " } catch (e) {\n", - " console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n", - " }\n", - " }\n", - " };\n", - "}\n", - "\n", - "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", - "mpl.findpos = function(e) {\n", - " //this section is from http://www.quirksmode.org/js/events_properties.html\n", - " var targ;\n", - " if (!e)\n", - " e = window.event;\n", - " if (e.target)\n", - " targ = e.target;\n", - " else if (e.srcElement)\n", - " targ = e.srcElement;\n", - " if (targ.nodeType == 3) // defeat Safari bug\n", - " targ = targ.parentNode;\n", - "\n", - " // jQuery normalizes the pageX and pageY\n", - " // pageX,Y are the mouse positions relative to the document\n", - " // offset() returns the position of the element relative to the document\n", - " var x = e.pageX - $(targ).offset().left;\n", - " var y = e.pageY - $(targ).offset().top;\n", - "\n", - " return {\"x\": x, \"y\": y};\n", - "};\n", - "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * http://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys (original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object')\n", - " obj[key] = original[key]\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", - "mpl.figure.prototype.mouse_event = function(event, name) {\n", - " var canvas_pos = mpl.findpos(event)\n", - "\n", - " if (name === 'button_press')\n", - " {\n", - " this.canvas.focus();\n", - " this.canvas_div.focus();\n", - " }\n", - "\n", - " var x = canvas_pos.x * mpl.ratio;\n", - " var y = canvas_pos.y * mpl.ratio;\n", - "\n", - " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step,\n", - " guiEvent: simpleKeys(event)});\n", - "\n", - " /* This prevents the web browser from automatically changing to\n", - " * the text insertion cursor when the button is pressed. We want\n", - " * to control all of the cursor setting manually through the\n", - " * 'cursor' event from matplotlib */\n", - " event.preventDefault();\n", - " return false;\n", - "}\n", - "\n", - "mpl.figure.prototype._key_event_extra = function(event, name) {\n", - " // Handle any extra behaviour associated with a key event\n", - "}\n", - "\n", - "mpl.figure.prototype.key_event = function(event, name) {\n", - "\n", - " // Prevent repeat events\n", - " if (name == 'key_press')\n", - " {\n", - " if (event.which === this._key)\n", - " return;\n", - " else\n", - " this._key = event.which;\n", - " }\n", - " if (name == 'key_release')\n", - " this._key = null;\n", - "\n", - " var value = '';\n", - " if (event.ctrlKey && event.which != 17)\n", - " value += \"ctrl+\";\n", - " if (event.altKey && event.which != 18)\n", - " value += \"alt+\";\n", - " if (event.shiftKey && event.which != 16)\n", - " value += \"shift+\";\n", - "\n", - " value += 'k';\n", - " value += event.which.toString();\n", - "\n", - " this._key_event_extra(event, name);\n", - "\n", - " this.send_message(name, {key: value,\n", - " guiEvent: simpleKeys(event)});\n", - " return false;\n", - "}\n", - "\n", - "mpl.figure.prototype.toolbar_button_onclick = function(name) {\n", - " if (name == 'download') {\n", - " this.handle_save(this, null);\n", - " } else {\n", - " this.send_message(\"toolbar_button\", {name: name});\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n", - " this.message.textContent = tooltip;\n", - "};\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", - "\n", - "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", - " // Create a \"websocket\"-like object which calls the given IPython comm\n", - " // object with the appropriate methods. Currently this is a non binary\n", - " // socket, so there is still some room for performance tuning.\n", - " var ws = {};\n", - "\n", - " ws.close = function() {\n", - " comm.close()\n", - " };\n", - " ws.send = function(m) {\n", - " //console.log('sending', m);\n", - " comm.send(m);\n", - " };\n", - " // Register the callback with on_msg.\n", - " comm.on_msg(function(msg) {\n", - " //console.log('receiving', msg['content']['data'], msg);\n", - " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", - " ws.onmessage(msg['content']['data'])\n", - " });\n", - " return ws;\n", - "}\n", - "\n", - "mpl.mpl_figure_comm = function(comm, msg) {\n", - " // This is the function which gets called when the mpl process\n", - " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", - "\n", - " var id = msg.content.data.id;\n", - " // Get hold of the div created by the display call when the Comm\n", - " // socket was opened in Python.\n", - " var element = $(\"#\" + id);\n", - " var ws_proxy = comm_websocket_adapter(comm)\n", - "\n", - " function ondownload(figure, format) {\n", - " window.open(figure.imageObj.src);\n", - " }\n", - "\n", - " var fig = new mpl.figure(id, ws_proxy,\n", - " ondownload,\n", - " element.get(0));\n", - "\n", - " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", - " // web socket which is closed, not our websocket->open comm proxy.\n", - " ws_proxy.onopen();\n", - "\n", - " fig.parent_element = element.get(0);\n", - " fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n", - " if (!fig.cell_info) {\n", - " console.error(\"Failed to find cell for figure\", id, fig);\n", - " return;\n", - " }\n", - "\n", - " var output_index = fig.cell_info[2]\n", - " var cell = fig.cell_info[0];\n", - "\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_close = function(fig, msg) {\n", - " var width = fig.canvas.width/mpl.ratio\n", - " fig.root.unbind('remove')\n", - "\n", - " // Update the output cell to use the data from the current canvas.\n", - " fig.push_to_output();\n", - " var dataURL = fig.canvas.toDataURL();\n", - " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", - " // the notebook keyboard shortcuts fail.\n", - " IPython.keyboard_manager.enable()\n", - " $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n", - " fig.close_ws(fig, msg);\n", - "}\n", - "\n", - "mpl.figure.prototype.close_ws = function(fig, msg){\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", - "}\n", - "\n", - "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", - " // Turn the data on the canvas into data in the output cell.\n", - " var width = this.canvas.width/mpl.ratio\n", - " var dataURL = this.canvas.toDataURL();\n", - " this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n", - "}\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function() {\n", - " // Tell IPython that the notebook contents must change.\n", - " IPython.notebook.set_dirty(true);\n", - " this.send_message(\"ack\", {});\n", - " var fig = this;\n", - " // Wait a second, then push the new image to the DOM so\n", - " // that it is saved nicely (might be nice to debounce this).\n", - " setTimeout(function () { fig.push_to_output() }, 1000);\n", - "}\n", - "\n", - "mpl.figure.prototype._init_toolbar = function() {\n", - " var fig = this;\n", - "\n", - " var nav_element = $('<div/>');\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) { continue; };\n", - "\n", - " var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></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 = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n", - " var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></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", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\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<ncells; i++) {\n", - " var cell = cells[i];\n", - " if (cell.cell_type === 'code'){\n", - " for (var j=0; j<cell.output_area.outputs.length; j++) {\n", - " var data = cell.output_area.outputs[j];\n", - " if (data.data) {\n", - " // IPython >= 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": [ - "<IPython.core.display.Javascript object>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABQAAAAPACAYAAABq3NR5AAAgAElEQVR4XuzdB7hdVZU48PVSgIA0AelqaBJBkJKYiIDSSQBnkLEOMoiC2AXRoUgxECIW/DsiVkQZxREYe2giA4KEJmIktAChCdIEQgmkvP93rxIpyX3n3nvuPefs83vfxzfqO2fvtX5ru2f2mrvfHRgcHBwMPwQIECBAgAABAgQIECBAgAABAgQIJCkwoAGYZF0lRYAAAQIECBAgQIAAAQIECBAgQKApoAFoIRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIE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e29svPHGceONNy5RofH7m2++OdZZZ5246667cv1bgDagBBafFAgQIECAAAECBAgQIECg9ALO36UvUV8CdAW4L8y9m6TdBuDtt9++6DrvQQcd1PySjyX9NH7/3JeENN577qpxHtnYgPJQNAYBAgQIECBAgAABAgQIEGgt4PxthTQENAArvg7abQD++te/jj322KOZ9cknnxyf+MQnlijQ+P0hhxzS/H3jvYkTJ+amVdUN6IrbHo7D//dP8aW3bx5bverluXkYiAABAgQIECBAgAABAgQI9EKgqufvXljUeUwNwIpXv90GYOMTfwcffHAz67POOiv22WefJQqcffbZ8W//9m/N3zfea3wiMOtPY4Np9XPffffFuHHjmo/cfffdzWvGZf656+GnYrsvXPyCECdttmac8u4tyxy22AgQIECAAAECBAgQIECg5gIagDVfAP9IXwOw4uug3QbgF77whfj0pz/dzPrcc8+N3XbbbYkCjd8/96m/L37xi3HooYdm1hoYGMj8bBUagN+7/I447pczF5vT7KmTMufqQQIECBAgQIAAAQIECBAg0E8BDcB+apd3Lg3A8tYmU2TtNgAnT54cRx99dHPsiy66KHbYYYclzvPb3/42dtxxx+bvG+8dddRRmWJqPJRaA/CpZ+fHa48+f4n53zR5t1hm5PDMPh4kQIAAAQIECBAgQIAAAQL9ENAA7Idy+efQACx/jVpG2G4DsF+fAEztCnCjCK/+z1+3rMV2G60WP3jf3681+yFAgAABAgQIECBAgAABAmUQ0AAsQxWKj0EDsPgadBVBuw3Afv0NwKGSquoGNFQTsJG3K8FDVd/vCRAgQIAAAQIECBAgQKBfAlU9f/fLpy7zaABWvNLtNgB/9atfxZ577tnM2rcAd1b8b196e5ww7caWL19/9C6x4rIjO5vAWwQIECBAgAABAgQIECBAICcBDcCcICs+jAZgxQvYbgPw9ttvj/XXX7+ZdeNbfRufCFzST+P33/rWt5q/brw3evTo3LSqvgHNX7AwNjjy3JYeqy2/dFx95E65mRmIAAECBAgQIECAAAECBAi0K1D183e7+Xp+8QIagBVfGe02AAcHB2OdddaJv/zlL7HxxhvHjTcu+ZNsY8aMiZtuuinWXnvtaHxTbztf7DEUayobkCvBQ1Xa7wkQIECAAAECBAgQIECgSIFUzt9FGqYwtwZgxavYbgOwke6HPvShOPXUU5uZX3HFFTF+/PiXKEyfPj0mTJjQ/M8bz59yyim5SqW0AR33yxvie5fPbunzx6N3jpWWXSpXQ4MRIECAAAECBAgQIECAAIGhBFI6fw+Vq98vWUADsOKro5MG4C233BKbbLJJzJ8/P7beeuu49NJLY9SoUYsknn766dhuu+3immuuiREjRsTMmTNjww03zFUqtQ1o4cLBWO+IaS2Nhg1E3H7ipFwdDUaAAAECBAgQIECAAAECBFoJpHb+Vu3OBDQAO3Mr7K3LLrssZs2atWj+hx56KA477LDmv99mm23i/e9//wti+4//+I/Fxnr44YfH1KlTm7/bYost4jOf+UzzbwPedttt8fnPfz6uu+665u8az02ZMiX3fFPdgFwJzn2pGJAAAQIECBAgQIAAAQIEuhBI9fzdBUktX9UArFjZGw2973//+5mjbvzNv8X9LFy4MD7wgQ/EaaedtsSxDjjggOaXgAwbNizzfFkfTHkDOufae+LQs65vSXHZZ94S66y8bFYuzxEgQIAAAQIECBAgQIAAgY4EUj5/dwRS05c0ACtW+LwagM+lPW3atGaT7+qrr47GpwlXXXXVGDt2bPMbgnffffee6aS+ATUar6MPb30luIE7e6orwT1bZAYmQIAAAQIECBAgQIAAgUj9/K3E2QQ0ALM5eSpngbpsQFmuBN9x4sRcv2E551IZjgABAgQIECBAgAABAgQqLFCX83eFS9SX0DUA+8JskhcL1GkDuuSWB2O/065quQguOnT7WH+1l1koBAgQIECAAAECBAgQIEAgV4E6nb9zhUtsMA3AxApalXTqtgG5ElyVlSlOAgQIECBAgAABAgQIpCVQt/N3WtXLLxsNwPwsjdSGQF03oCxXgv1dwDYWkkcJECBAgAABAgQIECBAoKVAXc/flsULBTQArYhCBOq8Af3hrr/F3l//fUv38z6xbWy8xgqF1MakBAgQIECAAAECBAgQIJCOQJ3P3+lUsftMNAC7NzRCBwJ134BcCe5g0XiFAAECBAgQIECAAAECBNoWqPv5u22wRF/QAEy0sGVPywb09wpluRLsW4LLvprFR4AAAQIECBAgQIAAgfIKOH+Xtzb9jEwDsJ/a5lokYAP652K45a9zYpeTL225On5y0IQYN/rlVhABAgQIECBAgAABAgQIEGhL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PyYMGjSop+PSGQEC8wrYf5sVUUAB0DzoU+DVV18NiyyySONnY8eODbEgmOfHApSnprYIECBAgAABAgQI9F7g/qdfCTt8N/u7xc/5/AZhm9WX7v3g9EiAwGwB+2+TQQHQHOhX4IUXXgjvfve7Gz+PTwL+9re/zVXLApQrp8YIECBAgAABAgQIFCbQzNOAUyaMLWx8OiZQdwH777rPgH/G7wlA86BPgV//+tdh1113bfzsG9/4Rjj55JNzlbIA5cqpMQIECBAgQIAAAQKFCjRTBHQkuNAU6bzGAvbfNU7+HKErAJoH8wjMmDEjbLLJJmHSpEmNn918881hgw02aEkqLjBZnyeffDKMGjWqccmjjz4alltuuZbadzEBAgQIECBAgAABAuUSeOzF18Nm3746c1D/s+/GYaOV/nnSyIcAgd4IKAD2xrnsvSgAlj1DBYzv1FNPDYceemij54997GPhwgsvbHkUrXzZrwJgy7xuIECAAAECBAgQIFBKAW8JLmVaDKrmAgqANZ8A/xu+AqB5MJfANddcE7bbbrswbdq0MGLEiPDXv/41LL1061/aqwBoYhEgQIAAAQIECBCor4AjwfXNvcjLJ6AAWL6cFDEiBcAi1Eva51133RU233zz8OKLL4b55psvXHHFFWHLLbdsa7SOALfF5iYCBAgQIECAAAECyQg888qbYdSJV2XGM37XdcKnRy2fTMwCIVBGAQXAMmal92NSAOy9eSl7nDx5cthss83CE088EYYMGRJ++ctfNo7/dutjAeqWrHYJECBAgAABAgQIlEugmacBvSW4XDkzmrQE7L/Tyme70SgAtiuX0H2x6Bef/Hv44YdDPLp77rnnhr322qurEVqAusqrcQIECBAgQIAAAQKlEtjuO9eEB595NXNMD564Uxg6ZHCpxm0wBFIQsP9OIYudx6AA2LlhpVt47rnnGsd877777kYc3//+98NXvvKVrsdkAeo6sQ4IECBAgAABAgQIlEqgmSPBh2y/ajho25GlGrfBEKi6gP131TOYz/gVAPNxrGQrU6dODdtss0249dZbG+OfMGFC+OY3v9mTWCxAPWHWCQECBAgQIECAAIHSCTgSXLqUGFDiAvbfiSe4yfAUAJuESu2y119/Peywww7hhhtuaIR2xBFHhBNOOKFnYVqAekatIwIECBAgQIAAAQKlE9hk/FXhyalv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oxsDOOqoo8Kxxx6by2AsQLkwaoQAAQIECBAgQIAAAQIECGQK2H+bIFFAAbCm8+Dwww8P48ePb0R/0003hY022qhPiQkTJoRx48Y1fnbFFVeEHXbYIRcxC1AujBohQIAAAQIECBAgQIAAAQIKgObAgAIKgAMSpXnBpEmTZhf99ttvv3DmmWfOE+iMGTPC2muvHe65556w2GKLhWeeeSYMGzYsFxAFwFwYNUKAAAECBAgQIECAAAECBBQAzYEBBRQAByRK94JZx4Dj8d9rr702bLLJJnMFe8opp4TDDjus8c+OPvrocMwxx+SGoQCYG6WGCBAgQIAAAQIECBAgQIBAvwL23yZHFFAArPE8uO2228Kmm24a3njjjbDwwguHeCx46623bvz/8847L5x11lkNnVVXXTXccsstYZFFFslNywKUG6WGCBAgQIAAAQIECBAgQICAAqA5kCmgAFjzCXLxxReHz372s+Hll1/uUyIW/y699NKwyiqr5CqlAJgrp8YIECBAgAABAgQIECBAgECfAvbfJkYUUAA0D8Lf//73cPrppzcKfXFhGD58eKPgt/vuu4cDDzwwLLjggrkrWYByJ9UgAQIECBAgQIAAAQIECBCYR8D+26RQADQHChOwABVGr2MCBAgQIECAAAECBAgQqJGA/XeNkp0RqicAzYNCBCxAhbDrlAABAgQIECBAgAABAgRqJmD/XbOE9xOuAqB5UIiABagQdp0SIECAAAECBAgQIECAQM0E7L9rlnAFQAkvk4AFqEzZMBYCBAgQIECAAAECBAgQSFXA/jvVzLYWlycAW/NydU4CFqCcIDVDgAABAgQIECBAgAABAgQyBOy/TY8ooABoHhQiYAEqhF2nBAgQIECAAAECBAgQIFAzAfvvmiW8n3AVAM2DQgQsQIWw65QAAQIECBAgQIAAAQIEaiZg/12zhCsASniZBCxAZcqGsRAgQIAAAQIECBAgQIBAqgL236lmtrW4PAHYmpercxKwAOUEqRkCBAgQIECAAAECBAgQIJAhYP9tekQBBUDzoBABC1Ah7DolQIAAAQIECBAgQIAAgZoJ2H/XLOH9hKsAaB4UImABKoRdpwQIECBAgAABAgQIECBQMwH775olXAFQwsskYAEqUzaMhQABAgQIECBAgAABAgRSFbD/TjWzrcXlCcDWvFydk4AFKCdIzRAgQIAAAQIECBAgQIAAgQwB+2/TIwooAJoHhQhYgAph1ykBAgQIECBAgAABAgQI1EzA/rtmCe8nXAVA86AQAQtQIew6JUCAAAECBAgQIECAAIGaCdh/1yzhCoASXiYBC1CZsmEsBAgQIECAAAECBAgQIJCqgP13qpltLS5PALbm5eqcBCxAOUFqhgABAgQIECBAgAABAgQIZAjYf5seUUAB0DwoRMACVAi7TgkQIECAAAECBAgQIECgZgL23zVLeD/hKgCaB4UIWIAKYdcpAQIECBAgQIAAAQIECNRMwP67ZglXAJTwMglYgMqUDWMhQIAAAQIECBAgQIAAgVQF7L9TzWxrcXkCsDUvV+ckMGXKlLDiiis2Wps0aVJ4z3vek1PLmiFAgAABAgQIECBAgAABAgRmCTz55JNh1KhRjf87efLksMIKK8CpoYACYA2TXoaQb7755tkLUBnGYwwECBAgQIAAAQIECBAgQCB1gfgAzoYbbph6mOLrQ0AB0LQoREABsBB2nRIgQIAAAQIECBAgQIBAjQUUAOubfAXA+ua+0MjffPPNcOeddzbGsNRSS4WhQ4cWOp5mOp/zsWnHlpsRc02ZBcznMmfH2NoRMKfbUXNPWQXM57JmxrjaFTCn25VzXxkFqjifp02bFp599tkG5zrrrBPmn3/+MtIaU5cFFAC7DKz5dAR8cWo6uRRJCOazWZCagDmdWkbrHY/5XO/8pxi9OZ1iVusbk/lc39xXPXIFwKpn0Ph7JmCh7xm1jnogYD73AFkXPRUwp3vKrbMuC5jPXQbWfM8FzOmek+uwiwLmcxdxNd1VAQXArvJqPCUBC31K2RSL+WwOpCZgTqeW0XrHYz7XO/8pRm9Op5jV+sZkPtc391WPXAGw6hk0/p4JWOh7Rq2jHgiYzz1A1kVPBczpnnLrrMsC5nOXgTXfcwFzuufkOuyigPncRVxNd1VAAbCrvBpPScBCn1I2xWI+mwOpCZjTqWW03vGYz/XOf4rRm9MpZrW+MZnP9c191SNXAKx6Bo2/ZwIW+p5R66gHAuZzD5B10VMBc7qn3DrrsoD53GVgzfdcwJzuObkOuyhgPncRV9NdFVAA7CqvxlMSsNCnlE2xmM/mQGoC5nRqGa13POZzvfOfYvTmdIpZrW9M5nN9c1/1yBUAq55B4++ZgIW+Z9Q66oGA+dwDZF30VMCc7im3zrosYD53GVjzPRcwp3tOrsMuCpjPXcTVdFcFFAC7yqtxAgQIECBAgAABAgQIECBAgAABAsUKKAAW6693AgQIECBAgAABAgQIECBAgAABAl0VUADsKq/GCRAgQIAAAQIECBAgQIAAAQIECBQroABYrL/eCRAgQIAAAQIECBAgQIAAAQIECHRVQAGwq7waJ0CAAAECBAgQIECAAAECBAgQIFCsgAJgsf56J0CAAAECBAgQIECAAAECBAgQINBVAQXArvJqnAABAgQIECBAgAABAgQIECBAgECxAgqAxfrrnQABAgQIECBAgAABAgQIECBAgEBXBRQAu8qrcQIECBAgQIAAAQIECBAgQIAAAQLFCigAFuuvdwIECBAgQIAAAQIECBAgQIAAAQJdFVAA7CqvxgkQIECAAAECBAgQIECAAAECBAgUK6AAWKy/3isi8Mgjj4QzzjgjXHrppSH+7/nmmy+sssoq4ROf+EQ44IADwoILLliRSAyzzgK33npruPzyy8N1110X/va3v4VnnnkmDBs2LCy77LJh9OjRYe+99w6bb755nYnEnoDAYYcdFk455ZTZkVx99dVhq622SiAyIdRF4LnnngvnnHNOuOiii8JDDz0UXnzxxfDud787vO997wtbbLFF2HXXXcMmm2xSFw5xVlzgrbfeCj/96U/DL3/5y3DHHXeEF154ofG7x3vf+96w6aabhn333TdsvPHGFY/S8KssEH8fnjRpUuM/N998c+M/zz//fCOkz33uc+Hcc89tKbz4u/ZZZ53VaO/ZZ58NSy21VBg1alRjru+4444tteViAnkLKADmLaq95ARi0W+PPfYIU6dO7TO21VZbLUycODGstNJKycUuoHQEttxyy3DttdcOGNCee+4Zzj777DB8+PABr3UBgbIJxM3lBhtsEKZNm6YAWLbkGE9TArFI8uUvf3n25rOvmz760Y+G3/zmN0215yICRQo8+uijYezYseHOO+/MHMbBBx8cTj311DBo0KAih6vvmgpkzbtWCoAzZ84M+++/f6P4198nFgHPPPNMc72mc60MYSsAliELxlBagbiZjE9Gvf7662HhhRcO48aNC1tvvXV44403wnnnnRd+9KMfNca++uqrN/5aFK/xIVBGgfjEanySJD7tt/vuuzee9Ft++eXD9OnTw4033tj4xfvxxx9vDP3Tn/50+PnPf17GMIyJQL8CM2bMaDxFEtfiESNGNJ5wjR9PAJo0VRH4yU9+Er7whS+EOJfjHI6FwM022ywsscQS4amnnmqs4RdffHFYdNFFG09T+RAos0D8Q8yHPvSh2cW/ddddNxxyyCEh/uH8lVdeCddff33jd4/XXnutEcbJJ58cvvGNb5Q5JGNLVGDOAmB80nqNNdYIV155ZSPaVgqARxxxRDjppJMa933wgx8M8UTCyiuv3Fi74/y+7bbbGj+L151wwgmJagqr7AIKgGXPkPEVKhCLfX/84x/D0KFDG09PvfPITTxmFhf3+Dn22GPDUUcdVeh4dU6gP4GPfOQjYa+99gq77bZbGDJkyDyXxSNn8SjO/fff3/hZnO+OA5tPVRI47bTTQnyKJP5B5mMf+1gYP358Y/gKgFXKYn3Hes899zQ2jP/4xz8aa++sQl9fIvFIpae06ztXqhL5BRdcED7+8Y83hht/f45fP/LO3z/+8pe/NH729ttvh8UXX7zxh5v4O7cPgV4KHH300WHDDTds/GfppZcOU6ZMCSuuuGJjCM0WAB988MFG4TAWvuNJhPh79AILLDA7jPgwSTyNc8sttzTm+L333tsoDvoQ6LWAAmCvxfVXGYH4FEn8vob42W+//RqPa7/zE/9Kv/baa4f4i3v8xeXpp59ufK+JD4EqClxyySVh5513bgz9oIMOCqeffnoVwzDmGgrEY2ZrrrlmePXVVxsFv/iHm/hHmfhRAKzhhKhgyNttt1246qqrwpJLLtn4nSL+tw+BKgvEp/2++93vNkL47W9/O/v3i3fGFL/T8te//nXjH8ejwvH3ah8CRQq0UwD8yle+En74wx82hh1P1vT1vZY33XTT7IdJDjzwwPC9732vyDD1XVMBBcCaJl7YAwvM+Rh3XLA32mijPm+aMGFC42hw/MTHxbfffvuBG3cFgRIKxOLJIoss0hhZ/M6eWBD0IVAFgVi4jvN11l/qjznmGAXAKiTOGBsC8UmQ+ORI/MS5G59G8SFQdYFY4PjBD37QCCO+eGyttdbqM6R47Pff//3fGz+LT0etv/76VQ/d+Csu0GoBMH73Xzw6HL9KJ55CiH/E6e8Tf37fffeF5ZZbrvFiSd97WfHJUsHhKwBWMGmG3BuB+Ka9eFxhoYUWCi+99FK/RxLiX3ni9wTGTzwCPOupk96MUi8E8hOIb+aLb5qMn1hQiX+x9yFQdoHzzz8/fPKTn2x8T1ospMS37SkAlj1rxjenwPHHHz/7K0TuuuuuxtOs8RPf/hu/niHO7VlrMzkCVRE444wzwte+9rXGcJt5AjAWQuLv2+9617uqEqJxJirQagHw4Ycfnn2ct79TY7Oo4s9nvSQk3jfrqHGilMIqoYACYAmTYkjlEIibyPiL93rrrRduv/32fgcVf0GPv5zHT3y5QtyM+hCookA8ghOP4sRP/It8/MJiHwJlFoibxfjkVHxBQnwp0z777NMYrgJgmbNmbO8UiE9cT5w4sfFyj/g7RXwJU1x///rXv86+NG4S4xOuX//6171wzBSqhMCzzz4b4gvIXn755cZ3DF9zzTXzfAdgfClCPCoZv9fSC8gqkdZaDLLVAuCll14a4ndtx0889v6v//qv/TrFn8fj8fET7xszZkwtTAVZHgEFwPLkwkhKJPDmm2/O/uLWZo5Cxrf/xreYxV9i4hOBPgSqJhC/zzJ+EfekSZMaQ4/fgRm/xNiHQJkF9t1330bhLz6FHd8oOesojQJgmbNmbO8UiMW9uOGMf3CMb/2ddWyyL6n4/WhXXHFF443uPgTKLhD/sLjHHnuEN954o/GSm1gYWXXVVRvf13rDDTc03gIc3wj8gQ98IFx22WVhmWWWKXtIxlcDgVYLgPF74uNb2+MnvqF91stv+qL61a9+1XhgJH7iffGJQB8CvRRQAOyltr4qIxD/ajlixIjGeOPRsvPOOy9z7PGNUfHNZfEX8/gFxj4EqiYQfwk/9NBDG8OOb1C98MILqxaC8dZMIBb84lc1xLdK3nrrrWGdddaZLaAAWLPJUPFw45N/8Smp+eabr/EW4MUWWyzE7xeOT2TH45Dx94r4FSOxQBI/seAdv6Jk8ODBFY/c8OsgcPfdd4fvfOc74Zxzzgnxu9Lm/MTfn7/5zW+G+Mec+JU7PgTKINBqAfCUU04Jhx12WGPocZ3ecccd+w0j/nzWU3/xuy/jU90+BHopoADYS219VUYgvlFy+eWXb4x3zz33DD/5yU8yxx6vjffE17nH18D7EKiSQDyWE99AOW3atEbhOx47i7+U+xAoq0A8LhafGIlftN3XcXUFwLJmzrj6Ehg6dGiYPn1640exoB2L2+98g2R8SjseMZtVBBzoKRPSBMog8Pbbbze+Gzs+qR3/UN7XZ8MNN2y8+CaeuPEhUAaBVguAc36Pa3yb+zbbbNNvGH/4wx/Ctttu2/h5vO9b3/pWGUI2hhoJKADWKNlCbV7AE4DNW7my2gLxC+c333zzxvdOxadP4tGyLbfcstpBGX3yArMKfPGPL/Hpknc+OaIAmPwUSCrAWV8jEoP61Kc+FX7xi1/0GV9cr+NJg/iJTwdecMEFSTkIJi2B+NU48Umna6+9tlHYjk86feELXwgrrbRSiF+18+c//zkcd9xxs7++IX432qyXhqQlIZqqCbRaAPQEYNUyXO/xKgDWO/+i70fAdwCaGnUQmDx5cuP7pp544onGL+fxiZJ4/NeHQJkF4pt+43elxacAL7roorDLLrvMM1wFwDJn0NjeKfCe97yn8SKb+Pnxj38c9tprr36RlltuufD444+H973vfeGRRx6BSaC0AvFrReLXi8TPueee23iJzTs/8eTBDjvsEK6++urGkfb4UpB11123tDEZWD0EWi0A+g7AesyLVKJUAEwlk+LIXcBbgHMn1WCJBGLRLz759/DDDzdenBB/Oc/adJZo6IZSc4H4hdlnnXVW4ymSE088sU+N+CXbs56OOvLII8Oaa67ZuG7nnXf2PVM1nz9lDH/UqFGNFy/Fz0DHx+LLmm666abGE9vxj5U+BMooEL/rb8kllwwvvPBC46Uf9913X7/DjC8DiX+MjJ/4kpD4JKAPgSIFWi0AXnLJJY3fL+LHW4CLzJy+mxFQAGxGyTW1FIhfLh+/ZDseLXvppZdC/I6evj7xrb/xC7njJ35Jd/yuEx8CZRZ47rnnGsd849HJ+Pn+978fvvKVr5R5yMZGYLbA5z//+cZTUu184lOvK6ywQju3uodA1wTiscj4R5j4ufLKK8P2229agysgAAAOVUlEQVTfb1+zioXxd5P4JlUfAmUUiE+0xidb42egl+nNeeomvjxh1vdcljEuY6qHQKsFwPjH9Pg98PET/0gZnwjs7zPrj5jx5/G++BZ4HwK9FFAA7KW2violcPjhh4fx48c3xhz/2r7RRhv1Of74pr5x48Y1fha/Py0eZfAhUFaBqVOnNr6cOL41NX7i/I1v4PMhUBUBBcCqZMo4mxX47//+7/DFL36xcfl//Md/hP3337/fW+NTVc8///yAT1U127frCHRDIP6hMZ6kiZ/ddtstxKey+/u88sorjbddx0980c3FF1/cjSFpk0DTAq0WAOMTr/HrGeLpmtVXX73xgrL+PmussUaIX2Xy3ve+t/ECyXgKx4dALwUUAHupra9KCUyaNGl20a+/v+bEt/LFL+SOC/1iiy3WeMPZsGHDKhWnwdZH4PXXX28UqONxm/g54ogjwgknnFAfAJHWRsB3ANYm1UkEGgt68Wmp+MbU+PRffAqwr098Y/tWW23V+NHee+8dzj777CTiF0R6AvH348UXXzy8/PLLYdlllw1///vf+z1JM+fxya9+9avhjDPOSA9ERJUSaLUAGIM74IADGn/AiZ94Ouydb3KP/zw+UBK/xmHW9T/4wQ8q5WKwaQgoAKaRR1F0SWDWMeB4/De+xWzWoj2ruznf+nT00UeHuOn0IVBGgfjChPj9JLM2lvFNe6eddloZh2pMBDoWUADsmFADPRaYc/MY3wIc3wY85yc+JRV/J7n99tsb/zj+kXLDDTfs8Sh1R6B5gc985jOz32gd1+T4e/I7Py+++GLj+/9mfSWJkzTN+7qyewLtFADvv//+sNZaa4X4YpsNNtigsW9cYIEFZg/yjTfeaKzht9xyS6MYHuf8yJEjuxeElgn0I6AAaGoQyBCIbyPbdNNNQ1y0F1544RCPBW+99daN/3/eeec1vog+fuIXHMcFfZFFFuFJoJQC8QjOhRde2BhbPAIci39Zxw6GDx/emNc+BKoooABYxazVe8zPPvtsY9MY3+wbN4fxGPCuu+7aOBp55513hm9/+9uNY2Px8+Uvfzn88Ic/rDeY6EsvEOfr+uuvH+Lpg/iJf4SMbwKOL3CK3/sXn4aKv4vMepv1tttuG37/+9+XPi4DTE/g+uuvDw8++ODswOIR9m984xuN/x/3gfvss89cQcevIunrE78SKn61Tvx88IMfbHzFTvxuwIceeqixhsd9ZfzE60466aT0IEVUCQEFwEqkySCLFIjfRfLZz362cYyhr08sklx66aVhlVVWKXKY+iaQKdDqd4y8//3vD/EvoD4EqiigAFjFrBlz/DqRXXbZZa6N6DtV4ncFxi+Y93Uj5ksVBGJB79Of/nSIBZWsT/zDZPyewHhs2IdArwVa/W7h+J1/fX3i0fcvfelL4Zxzzuk3hPj1DfEBksGDB/c6TP0RaAgoAJoIBJoQiN9dcvrppzcKfY899liIT0fFgt/uu+8eDjzwwLDgggs20YpLCBQnoABYnL2eey+gANh7cz3mI/Daa681vkcqFkMeeOCBxpt+R4wY0XgKJX4fcTyF4EOgSgLxOy7/67/+q/F237vuuiu89NJLjadcl1lmmcYx9nhUOBa+W/09pUoGxlpugbwKgLOinDhxYqPId/PNNzeK3/HlTXGuxzV8p512KjeG0SUvoACYfIoFSIAAAQIECBAgQIAAAQIECBAgUGcBBcA6Z1/sBAgQIECAAAECBAgQIECAAAECyQsoACafYgESIECAAAECBAgQIECAAAECBAjUWUABsM7ZFzsBAgQIECBAgAABAgQIECBAgEDyAgqAyadYgAQIECBAgAABAgQIECBAgAABAnUWUACsc/bFToAAAQIECBAgQIAAAQIECBAgkLyAAmDyKRYgAQIECBAgQIAAAQIECBAgQIBAnQUUAOucfbETIECAAAECBAgQIECAAAECBAgkL6AAmHyKBUiAAAECBAgQIECAAAECBAgQIFBnAQXAOmdf7AQIECBAgAABAgQIECBAgAABAskLKAAmn2IBEiBAgAABAgQIECBAgAABAgQI1FlAAbDO2Rc7AQIECBAgQIAAAQIECBAgQIBA8gIKgMmnWIAECBAgQIAAAQIECBAgQIAAAQJ1FlAArHP2xU6AAAECBAgQIECAAAECBAgQIJC8gAJg8ikWIAECBAgQIECAAAECBAgQIECAQJ0FFADrnH2xEyBAgAABAgQIECBAgAABAgQIJC+gAJh8igVIgAABAgQIECBAgAABAgQIECBQZwEFwDpnX+wECBAgQIAAAQIECBAgQIAAAQLJCygAJp9iARIgQIAAAQIECBAgQIAAAQIECNRZQAGwztkXOwECBAgQIECAAAECBAgQIECAQPICCoDJp1iABAgQIECAAAECBAgQIECAAAECdRZQAKxz9sVOgAABAgQIECBAgAABAgQIECCQvIACYPIpFiABAgQIECBAgAABAgQIECBAgECdBRQA65x9sRMgQIAAAQIEMgR+8YtfhM985jONK/bdd9/wn//5n31e/cgjj4R11103TJ06NYwcOTLcdtttYaGFFmJLgAABAgQIECBQEgEFwJIkwjAIECBAgAABAmUU+OxnPxt+9rOfNYb2m9/8Jnz0ox+da5gzZswI22yzTbjmmmvC0KFDw5/+9Kew4YYbljEUYyJAgAABAgQI1FZAAbC2qRc4AQIECBAgQGBggZdffjmst956YcqUKWHJJZcMd955Z1hmmWVm3/jtb3/7/7d3/y5V9XEcwD96FQJJbBcUwVEI/MEVDeQu5h7aIhS0tjQZ4RIiOAnSHyA6Nbi61KCkBC35A3JQHBtbKiIxjXOG5+HheeCpb9f6en2d7V7O53s+39fnTm/OuSdmZmbKz3Nzc/HkyZP/X9QZBAgQIECAAAECv1VAAPhbuV2MAAECBAgQIHD5BLa2tmJsbCy+ffsWt2/fjvX19Whqaiof9a1Wq3FychKjo6OxsbERlUrl8m1QxwQIECBAgACBBhcQADb4gG2PAAECBAgQIFAPgdnZ2fIOv+JYWlqKBw8eRH9/fxwcHER7e3vs7u5Gd3d3PS5lDQIECBAgQIAAgToLCADrDGo5AgQIECBAgEAjCpyensbIyEi8efMmrl27Vt4JWPwnYHGsrq5G8V+BDgIECBAgQIAAgTwFBIB5zkVXBAgQIECAAIHsBI6OjuLmzZvx+fPnv3q7e/duFG8LdhAgQIAAAQIECOQrIADMdzY6I0CAAAECBAhkJ1A8Blw8DlwcxUtBDg8Po6OjI7s+NUSAAAECBAgQIPC3gADQr4EAAQIECBAgQOCHBD59+lS+Efj4+Lg8v3gRyMuXL6NWq/1QvZMIECBAgAABAgT+jIAA8M+4uyoBAgQIECBA4NIJ3L9/P5aXl8u+r1+/Hh8/fozOzs7Y29uLGzduXLr9aJgAAQIECBAgcFUEBIBXZdL2SYAAAQIECBD4BYG1tbW4c+dOucK9e/diamoqJiYmys+Tk5Px/PnzX1hdKQECBAgQIECAwEUKCAAvUtfaBAgQIECAAIEGEHj//n309fXFhw8foqenJ3Z2dso7AB8+fBjPnj0rd7iyshLT09MNsFtbIECAAAECBAg0noAAsPFmakcECBAgQIAAgboJnJ+fx/j4eLx48SIqlUq8evUqhoeHy/W/fPkSAwMD8e7du2hvby8fBe7q6qrbtS1EgAABAgQIECBQHwEBYH0crUKAAAECBAgQaEiBxcXFePToUbm34u2/T58+/cc+3759G9VqNU5OTuLWrVuxsbERzc3NDWlhUwQIECBAgACByyogALysk9M3AQIECBAgQOCCBfb392NwcDC+fv0aQ0NDsb29HS0tLf+66sLCQszMzJTfz8/Px+PHjy+4M8sTIECAAAECBAj8jIAA8Ge0nEuAAAECBAgQuCICRehXhH9FCNjW1hbFnX69vb3/ufuzs7Oo1WqxubkZra2t8fr16+jv778iUrZJgAABAgQIEMhfQACY/4x0SIAAAQIECBAgQIAAAQIECBAgQCBZQACYTKeQAAECBAgQIECAAAECBAgQIECAQP4CAsD8Z6RDAgQIECBAgAABAgQIECBAgAABAskCAsBkOoUECBAgQIAAAQIECBAgQIAAAQIE8hcQAOY/Ix0SIECAAAECBAgQIECAAAECBAgQSBYQACbTKSRAgAABAgQIECBAgAABAgQIECCQv4AAMP8Z6ZAAAQIECBAgQIAAAQIECBAgQIBAsoAAMJlOIQECBAgQIECAAAECBAgQIECAAIH8BQSA+c9IhwQIECBAgAABAgQIECBAgAABAgSSBQSAyXQKCRAgQIAAAQIECBAgQIAAAQIECOQvIADMf0Y6JECAAAECBAgQIECAAAECBAgQIJAsIABMplNIgAABAgQIECBAgAABAgQIECBAIH8BAWD+M9IhAQIECBAgQIAAAQIECBAgQIAAgWQBAWAynUICBAgQIECAAAECBAgQIECAAAEC+QsIAPOfkQ4JECBAgAABAgQIECBAgAABAgQIJAsIAJPpFBIgQIAAAQIECBAgQIAAAQIECBDIX0AAmP+MdEiAAAECBAgQIECAAAECBAgQIEAgWUAAmEynkAABAgQIECBAgAABAgQIECBAgED+AgLA/GekQwIECBAgQIAAAQIECBAgQIAAAQLJAgLAZDqFBAgQIECAAAECBAgQIECAAAECBPIXEADmPyMdEiBAgAABAgQIECBAgAABAgQIEEgWEAAm0ykkQIAAAQIECBAgQIAAAQIECBAgkL+AADD/GemQAAECBAgQIECAAAECBAgQIECAQLKAADCZTiEBAgQIECBAgAABAgQIECBAgACB/AUEgPnPSIcECBAgQIAAAQIECBAgQIAAAQIEkgW+A8VLoWJRweHEAAAAAElFTkSuQmCC\" width=\"640\">" - ], - "text/plain": [ - "<IPython.core.display.HTML object>" - ] - }, - "metadata": {}, - "output_type": "display_data" + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2020-08-21T15:12:37.254118Z", + "start_time": "2020-08-21T15:12:37.169941Z" } - ], + }, + "outputs": [], "source": [ "plt.plot(x,y)\n", "plt.xlabel(\"x\")\n", @@ -1394,7 +875,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ad73b12e0c6f6822", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Clearly, this function has some tiny little blip around x=2 and another around x=5. As a curious physicist, I'd like to zoom in and see in more detail what these look like. \n", "\n", @@ -1405,7 +895,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1414,809 +904,25 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-34cc27094187c60d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "and then plot it again:" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "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 = $('<div/>');\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", - " fig.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", - " '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n", - " 'ui-helper-clearfix\"/>');\n", - " var titletext = $(\n", - " '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n", - " 'text-align: center; padding: 3px;\"/>');\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 = $('<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 = $('<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 = $('<canvas/>');\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 = $('<div/>');\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 = $('<button/>');\n", - " button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n", - " 'ui-button-icon-only');\n", - " button.attr('role', 'button');\n", - " button.attr('aria-disabled', 'false');\n", - " button.click(method_name, toolbar_event);\n", - " button.mouseover(tooltip, toolbar_mouse_event);\n", - "\n", - " var icon_img = $('<span/>');\n", - " icon_img.addClass('ui-button-icon-primary ui-icon');\n", - " icon_img.addClass(image);\n", - " icon_img.addClass('ui-corner-all');\n", - "\n", - " var tooltip_span = $('<span/>');\n", - " tooltip_span.addClass('ui-button-text');\n", - " tooltip_span.html(tooltip);\n", - "\n", - " button.append(icon_img);\n", - " button.append(tooltip_span);\n", - "\n", - " nav_element.append(button);\n", - " }\n", - "\n", - " var fmt_picker_span = $('<span/>');\n", - "\n", - " var fmt_picker = $('<select/>');\n", - " fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n", - " fmt_picker_span.append(fmt_picker);\n", - " nav_element.append(fmt_picker_span);\n", - " this.format_dropdown = fmt_picker[0];\n", - "\n", - " for (var ind in mpl.extensions) {\n", - " var fmt = mpl.extensions[ind];\n", - " var option = $(\n", - " '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n", - " fmt_picker.append(option);\n", - " }\n", - "\n", - " // Add hover states to the ui-buttons\n", - " $( \".ui-button\" ).hover(\n", - " function() { $(this).addClass(\"ui-state-hover\");},\n", - " function() { $(this).removeClass(\"ui-state-hover\");}\n", - " );\n", - "\n", - " var status_bar = $('<span class=\"mpl-message\"/>');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "}\n", - "\n", - "mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n", - " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", - " // which will in turn request a refresh of the image.\n", - " this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n", - "}\n", - "\n", - "mpl.figure.prototype.send_message = function(type, properties) {\n", - " properties['type'] = type;\n", - " properties['figure_id'] = this.id;\n", - " this.ws.send(JSON.stringify(properties));\n", - "}\n", - "\n", - "mpl.figure.prototype.send_draw_message = function() {\n", - " if (!this.waiting) {\n", - " this.waiting = true;\n", - " this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n", - " }\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype.handle_save = function(fig, msg) {\n", - " var format_dropdown = fig.format_dropdown;\n", - " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", - " fig.ondownload(fig, format);\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype.handle_resize = function(fig, msg) {\n", - " var size = msg['size'];\n", - " if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n", - " fig._resize_canvas(size[0], size[1]);\n", - " fig.send_message(\"refresh\", {});\n", - " };\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n", - " var x0 = msg['x0'] / mpl.ratio;\n", - " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", - " var x1 = msg['x1'] / mpl.ratio;\n", - " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", - " x0 = Math.floor(x0) + 0.5;\n", - " y0 = Math.floor(y0) + 0.5;\n", - " x1 = Math.floor(x1) + 0.5;\n", - " y1 = Math.floor(y1) + 0.5;\n", - " var min_x = Math.min(x0, x1);\n", - " var min_y = Math.min(y0, y1);\n", - " var width = Math.abs(x1 - x0);\n", - " var height = Math.abs(y1 - y0);\n", - "\n", - " fig.rubberband_context.clearRect(\n", - " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n", - "\n", - " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n", - " // Updates the figure title.\n", - " fig.header.textContent = msg['label'];\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_cursor = function(fig, msg) {\n", - " var cursor = msg['cursor'];\n", - " switch(cursor)\n", - " {\n", - " case 0:\n", - " cursor = 'pointer';\n", - " break;\n", - " case 1:\n", - " cursor = 'default';\n", - " break;\n", - " case 2:\n", - " cursor = 'crosshair';\n", - " break;\n", - " case 3:\n", - " cursor = 'move';\n", - " break;\n", - " }\n", - " fig.rubberband_canvas.style.cursor = cursor;\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_message = function(fig, msg) {\n", - " fig.message.textContent = msg['message'];\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_draw = function(fig, msg) {\n", - " // Request the server to send over a new figure.\n", - " fig.send_draw_message();\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n", - " fig.image_mode = msg['mode'];\n", - "}\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function() {\n", - " // Called whenever the canvas gets updated.\n", - " this.send_message(\"ack\", {});\n", - "}\n", - "\n", - "// A function to construct a web socket function for onmessage handling.\n", - "// Called in the figure constructor.\n", - "mpl.figure.prototype._make_on_message_function = function(fig) {\n", - " return function socket_on_message(evt) {\n", - " if (evt.data instanceof Blob) {\n", - " /* FIXME: We get \"Resource interpreted as Image but\n", - " * transferred with MIME type text/plain:\" errors on\n", - " * Chrome. But how to set the MIME type? It doesn't seem\n", - " * to be part of the websocket stream */\n", - " evt.data.type = \"image/png\";\n", - "\n", - " /* Free the memory for the previous frames */\n", - " if (fig.imageObj.src) {\n", - " (window.URL || window.webkitURL).revokeObjectURL(\n", - " fig.imageObj.src);\n", - " }\n", - "\n", - " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", - " evt.data);\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", - " fig.imageObj.src = evt.data;\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - "\n", - " var msg = JSON.parse(evt.data);\n", - " var msg_type = msg['type'];\n", - "\n", - " // Call the \"handle_{type}\" callback, which takes\n", - " // the figure and JSON message as its only arguments.\n", - " try {\n", - " var callback = fig[\"handle_\" + msg_type];\n", - " } catch (e) {\n", - " console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n", - " return;\n", - " }\n", - "\n", - " if (callback) {\n", - " try {\n", - " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", - " callback(fig, msg);\n", - " } catch (e) {\n", - " console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n", - " }\n", - " }\n", - " };\n", - "}\n", - "\n", - "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", - "mpl.findpos = function(e) {\n", - " //this section is from http://www.quirksmode.org/js/events_properties.html\n", - " var targ;\n", - " if (!e)\n", - " e = window.event;\n", - " if (e.target)\n", - " targ = e.target;\n", - " else if (e.srcElement)\n", - " targ = e.srcElement;\n", - " if (targ.nodeType == 3) // defeat Safari bug\n", - " targ = targ.parentNode;\n", - "\n", - " // jQuery normalizes the pageX and pageY\n", - " // pageX,Y are the mouse positions relative to the document\n", - " // offset() returns the position of the element relative to the document\n", - " var x = e.pageX - $(targ).offset().left;\n", - " var y = e.pageY - $(targ).offset().top;\n", - "\n", - " return {\"x\": x, \"y\": y};\n", - "};\n", - "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * http://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys (original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object')\n", - " obj[key] = original[key]\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", - "mpl.figure.prototype.mouse_event = function(event, name) {\n", - " var canvas_pos = mpl.findpos(event)\n", - "\n", - " if (name === 'button_press')\n", - " {\n", - " this.canvas.focus();\n", - " this.canvas_div.focus();\n", - " }\n", - "\n", - " var x = canvas_pos.x * mpl.ratio;\n", - " var y = canvas_pos.y * mpl.ratio;\n", - "\n", - " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step,\n", - " guiEvent: simpleKeys(event)});\n", - "\n", - " /* This prevents the web browser from automatically changing to\n", - " * the text insertion cursor when the button is pressed. We want\n", - " * to control all of the cursor setting manually through the\n", - " * 'cursor' event from matplotlib */\n", - " event.preventDefault();\n", - " return false;\n", - "}\n", - "\n", - "mpl.figure.prototype._key_event_extra = function(event, name) {\n", - " // Handle any extra behaviour associated with a key event\n", - "}\n", - "\n", - "mpl.figure.prototype.key_event = function(event, name) {\n", - "\n", - " // Prevent repeat events\n", - " if (name == 'key_press')\n", - " {\n", - " if (event.which === this._key)\n", - " return;\n", - " else\n", - " this._key = event.which;\n", - " }\n", - " if (name == 'key_release')\n", - " this._key = null;\n", - "\n", - " var value = '';\n", - " if (event.ctrlKey && event.which != 17)\n", - " value += \"ctrl+\";\n", - " if (event.altKey && event.which != 18)\n", - " value += \"alt+\";\n", - " if (event.shiftKey && event.which != 16)\n", - " value += \"shift+\";\n", - "\n", - " value += 'k';\n", - " value += event.which.toString();\n", - "\n", - " this._key_event_extra(event, name);\n", - "\n", - " this.send_message(name, {key: value,\n", - " guiEvent: simpleKeys(event)});\n", - " return false;\n", - "}\n", - "\n", - "mpl.figure.prototype.toolbar_button_onclick = function(name) {\n", - " if (name == 'download') {\n", - " this.handle_save(this, null);\n", - " } else {\n", - " this.send_message(\"toolbar_button\", {name: name});\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n", - " this.message.textContent = tooltip;\n", - "};\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", - "\n", - "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", - " // Create a \"websocket\"-like object which calls the given IPython comm\n", - " // object with the appropriate methods. Currently this is a non binary\n", - " // socket, so there is still some room for performance tuning.\n", - " var ws = {};\n", - "\n", - " ws.close = function() {\n", - " comm.close()\n", - " };\n", - " ws.send = function(m) {\n", - " //console.log('sending', m);\n", - " comm.send(m);\n", - " };\n", - " // Register the callback with on_msg.\n", - " comm.on_msg(function(msg) {\n", - " //console.log('receiving', msg['content']['data'], msg);\n", - " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", - " ws.onmessage(msg['content']['data'])\n", - " });\n", - " return ws;\n", - "}\n", - "\n", - "mpl.mpl_figure_comm = function(comm, msg) {\n", - " // This is the function which gets called when the mpl process\n", - " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", - "\n", - " var id = msg.content.data.id;\n", - " // Get hold of the div created by the display call when the Comm\n", - " // socket was opened in Python.\n", - " var element = $(\"#\" + id);\n", - " var ws_proxy = comm_websocket_adapter(comm)\n", - "\n", - " function ondownload(figure, format) {\n", - " window.open(figure.imageObj.src);\n", - " }\n", - "\n", - " var fig = new mpl.figure(id, ws_proxy,\n", - " ondownload,\n", - " element.get(0));\n", - "\n", - " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", - " // web socket which is closed, not our websocket->open comm proxy.\n", - " ws_proxy.onopen();\n", - "\n", - " fig.parent_element = element.get(0);\n", - " fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n", - " if (!fig.cell_info) {\n", - " console.error(\"Failed to find cell for figure\", id, fig);\n", - " return;\n", - " }\n", - "\n", - " var output_index = fig.cell_info[2]\n", - " var cell = fig.cell_info[0];\n", - "\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_close = function(fig, msg) {\n", - " var width = fig.canvas.width/mpl.ratio\n", - " fig.root.unbind('remove')\n", - "\n", - " // Update the output cell to use the data from the current canvas.\n", - " fig.push_to_output();\n", - " var dataURL = fig.canvas.toDataURL();\n", - " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", - " // the notebook keyboard shortcuts fail.\n", - " IPython.keyboard_manager.enable()\n", - " $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n", - " fig.close_ws(fig, msg);\n", - "}\n", - "\n", - "mpl.figure.prototype.close_ws = function(fig, msg){\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", - "}\n", - "\n", - "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", - " // Turn the data on the canvas into data in the output cell.\n", - " var width = this.canvas.width/mpl.ratio\n", - " var dataURL = this.canvas.toDataURL();\n", - " this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n", - "}\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function() {\n", - " // Tell IPython that the notebook contents must change.\n", - " IPython.notebook.set_dirty(true);\n", - " this.send_message(\"ack\", {});\n", - " var fig = this;\n", - " // Wait a second, then push the new image to the DOM so\n", - " // that it is saved nicely (might be nice to debounce this).\n", - " setTimeout(function () { fig.push_to_output() }, 1000);\n", - "}\n", - "\n", - "mpl.figure.prototype._init_toolbar = function() {\n", - " var fig = this;\n", - "\n", - " var nav_element = $('<div/>');\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) { continue; };\n", - "\n", - " var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></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 = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n", - " var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></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", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\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<ncells; i++) {\n", - " var cell = cells[i];\n", - " if (cell.cell_type === 'code'){\n", - " for (var j=0; j<cell.output_area.outputs.length; j++) {\n", - " var data = cell.output_area.outputs[j];\n", - " if (data.data) {\n", - " // IPython >= 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": [ - "<IPython.core.display.Javascript object>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABQAAAAPACAYAAABq3NR5AAAgAElEQVR4XuzdB7hdVZU48PVSgIA0AelqaBJBkJKYiIDSSQBnkLEOMoiC2AXRoUgxECIW/DsiVkQZxREYe2giA4KEJmIktAChCdIEQgmkvP93rxIpyX3n3nvuPefs83vfxzfqO2fvtX5ru2f2mrvfHRgcHBwMPwQIECBAgAABAgQIECBAgAABAgQIJCkwoAGYZF0lRYAAAQIECBAgQIAAAQIECBAgQKApoAFoIRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIECBAgQIAAAQIECBAgQIAAAQIagNYAAQIECBAgQIAAAQIECBAgQIAAgYQFNAATLq7UCBAgQIAAAQIECBAgQIAAAQIECGgAWgMECBAgQIAAAQIECBAgQIAAAQIEEhbQAEy4uFIjQIAAAQIECBAgQIAAAQIECBAgoAFoDRAgQIAAAQIECBAgQIAAAQIECBBIWEADMOHiSo0AAQIECBAgQIAAAQIECBAgQICABqA1QIAAAQIECBAgQIAAAQIECBAgQCBhAQ3AhIsrNQIE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e29svPHGceONNy5RofH7m2++OdZZZ5246667cv1bgDagBBafFAgQIECAAAECBAgQIECg9ALO36UvUV8CdAW4L8y9m6TdBuDtt9++6DrvQQcd1PySjyX9NH7/3JeENN577qpxHtnYgPJQNAYBAgQIECBAgAABAgQIEGgt4PxthTQENAArvg7abQD++te/jj322KOZ9cknnxyf+MQnlijQ+P0hhxzS/H3jvYkTJ+amVdUN6IrbHo7D//dP8aW3bx5bverluXkYiAABAgQIECBAgAABAgQI9EKgqufvXljUeUwNwIpXv90GYOMTfwcffHAz67POOiv22WefJQqcffbZ8W//9m/N3zfea3wiMOtPY4Np9XPffffFuHHjmo/cfffdzWvGZf656+GnYrsvXPyCECdttmac8u4tyxy22AgQIECAAAECBAgQIECg5gIagDVfAP9IXwOw4uug3QbgF77whfj0pz/dzPrcc8+N3XbbbYkCjd8/96m/L37xi3HooYdm1hoYGMj8bBUagN+7/I447pczF5vT7KmTMufqQQIECBAgQIAAAQIECBAg0E8BDcB+apd3Lg3A8tYmU2TtNgAnT54cRx99dHPsiy66KHbYYYclzvPb3/42dtxxx+bvG+8dddRRmWJqPJRaA/CpZ+fHa48+f4n53zR5t1hm5PDMPh4kQIAAAQIECBAgQIAAAQL9ENAA7Idy+efQACx/jVpG2G4DsF+fAEztCnCjCK/+z1+3rMV2G60WP3jf3681+yFAgAABAgQIECBAgAABAmUQ0AAsQxWKj0EDsPgadBVBuw3Afv0NwKGSquoGNFQTsJG3K8FDVd/vCRAgQIAAAQIECBAgQKBfAlU9f/fLpy7zaABWvNLtNgB/9atfxZ577tnM2rcAd1b8b196e5ww7caWL19/9C6x4rIjO5vAWwQIECBAgAABAgQIECBAICcBDcCcICs+jAZgxQvYbgPw9ttvj/XXX7+ZdeNbfRufCFzST+P33/rWt5q/brw3evTo3LSqvgHNX7AwNjjy3JYeqy2/dFx95E65mRmIAAECBAgQIECAAAECBAi0K1D183e7+Xp+8QIagBVfGe02AAcHB2OdddaJv/zlL7HxxhvHjTcu+ZNsY8aMiZtuuinWXnvtaHxTbztf7DEUayobkCvBQ1Xa7wkQIECAAAECBAgQIECgSIFUzt9FGqYwtwZgxavYbgOwke6HPvShOPXUU5uZX3HFFTF+/PiXKEyfPj0mTJjQ/M8bz59yyim5SqW0AR33yxvie5fPbunzx6N3jpWWXSpXQ4MRIECAAAECBAgQIECAAIGhBFI6fw+Vq98vWUADsOKro5MG4C233BKbbLJJzJ8/P7beeuu49NJLY9SoUYsknn766dhuu+3immuuiREjRsTMmTNjww03zFUqtQ1o4cLBWO+IaS2Nhg1E3H7ipFwdDUaAAAECBAgQIECAAAECBFoJpHb+Vu3OBDQAO3Mr7K3LLrssZs2atWj+hx56KA477LDmv99mm23i/e9//wti+4//+I/Fxnr44YfH1KlTm7/bYost4jOf+UzzbwPedttt8fnPfz6uu+665u8az02ZMiX3fFPdgFwJzn2pGJAAAQIECBAgQIAAAQIEuhBI9fzdBUktX9UArFjZGw2973//+5mjbvzNv8X9LFy4MD7wgQ/EaaedtsSxDjjggOaXgAwbNizzfFkfTHkDOufae+LQs65vSXHZZ94S66y8bFYuzxEgQIAAAQIECBAgQIAAgY4EUj5/dwRS05c0ACtW+LwagM+lPW3atGaT7+qrr47GpwlXXXXVGDt2bPMbgnffffee6aS+ATUar6MPb30luIE7e6orwT1bZAYmQIAAAQIECBAgQIAAgUj9/K3E2QQ0ALM5eSpngbpsQFmuBN9x4sRcv2E551IZjgABAgQIECBAgAABAgQqLFCX83eFS9SX0DUA+8JskhcL1GkDuuSWB2O/065quQguOnT7WH+1l1koBAgQIECAAAECBAgQIEAgV4E6nb9zhUtsMA3AxApalXTqtgG5ElyVlSlOAgQIECBAgAABAgQIpCVQt/N3WtXLLxsNwPwsjdSGQF03oCxXgv1dwDYWkkcJECBAgAABAgQIECBAoKVAXc/flsULBTQArYhCBOq8Af3hrr/F3l//fUv38z6xbWy8xgqF1MakBAgQIECAAAECBAgQIJCOQJ3P3+lUsftMNAC7NzRCBwJ134BcCe5g0XiFAAECBAgQIECAAAECBNoWqPv5u22wRF/QAEy0sGVPywb09wpluRLsW4LLvprFR4AAAQIECBAgQIAAgfIKOH+Xtzb9jEwDsJ/a5lokYAP652K45a9zYpeTL225On5y0IQYN/rlVhABAgQIECBAgAABAgQIEGhL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PyYMGjSop+PSGQEC8wrYf5sVUUAB0DzoU+DVV18NiyyySONnY8eODbEgmOfHApSnprYIECBAgAABAgQI9F7g/qdfCTt8N/u7xc/5/AZhm9WX7v3g9EiAwGwB+2+TQQHQHOhX4IUXXgjvfve7Gz+PTwL+9re/zVXLApQrp8YIECBAgAABAgQIFCbQzNOAUyaMLWx8OiZQdwH777rPgH/G7wlA86BPgV//+tdh1113bfzsG9/4Rjj55JNzlbIA5cqpMQIECBAgQIAAAQKFCjRTBHQkuNAU6bzGAvbfNU7+HKErAJoH8wjMmDEjbLLJJmHSpEmNn918881hgw02aEkqLjBZnyeffDKMGjWqccmjjz4alltuuZbadzEBAgQIECBAgAABAuUSeOzF18Nm3746c1D/s+/GYaOV/nnSyIcAgd4IKAD2xrnsvSgAlj1DBYzv1FNPDYceemij54997GPhwgsvbHkUrXzZrwJgy7xuIECAAAECBAgQIFBKAW8JLmVaDKrmAgqANZ8A/xu+AqB5MJfANddcE7bbbrswbdq0MGLEiPDXv/41LL1061/aqwBoYhEgQIAAAQIECBCor4AjwfXNvcjLJ6AAWL6cFDEiBcAi1Eva51133RU233zz8OKLL4b55psvXHHFFWHLLbdsa7SOALfF5iYCBAgQIECAAAECyQg888qbYdSJV2XGM37XdcKnRy2fTMwCIVBGAQXAMmal92NSAOy9eSl7nDx5cthss83CE088EYYMGRJ++ctfNo7/dutjAeqWrHYJECBAgAABAgQIlEugmacBvSW4XDkzmrQE7L/Tyme70SgAtiuX0H2x6Bef/Hv44YdDPLp77rnnhr322qurEVqAusqrcQIECBAgQIAAAQKlEtjuO9eEB595NXNMD564Uxg6ZHCpxm0wBFIQsP9OIYudx6AA2LlhpVt47rnnGsd877777kYc3//+98NXvvKVrsdkAeo6sQ4IECBAgAABAgQIlEqgmSPBh2y/ajho25GlGrfBEKi6gP131TOYz/gVAPNxrGQrU6dODdtss0249dZbG+OfMGFC+OY3v9mTWCxAPWHWCQECBAgQIECAAIHSCTgSXLqUGFDiAvbfiSe4yfAUAJuESu2y119/Peywww7hhhtuaIR2xBFHhBNOOKFnYVqAekatIwIECBAgQIAAAQKlE9hk/FXhyalv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oxsDOOqoo8Kxxx6by2AsQLkwaoQAAQIECBAgQIAAAQIECGQK2H+bIFFAAbCm8+Dwww8P48ePb0R/0003hY022qhPiQkTJoRx48Y1fnbFFVeEHXbYIRcxC1AujBohQIAAAQIECBAgQIAAAQIKgObAgAIKgAMSpXnBpEmTZhf99ttvv3DmmWfOE+iMGTPC2muvHe65556w2GKLhWeeeSYMGzYsFxAFwFwYNUKAAAECBAgQIECAAAECBBQAzYEBBRQAByRK94JZx4Dj8d9rr702bLLJJnMFe8opp4TDDjus8c+OPvrocMwxx+SGoQCYG6WGCBAgQIAAAQIECBAgQIBAvwL23yZHFFAArPE8uO2228Kmm24a3njjjbDwwguHeCx46623bvz/8847L5x11lkNnVVXXTXccsstYZFFFslNywKUG6WGCBAgQIAAAQIECBAgQICAAqA5kCmgAFjzCXLxxReHz372s+Hll1/uUyIW/y699NKwyiqr5CqlAJgrp8YIECBAgAABAgQIECBAgECfAvbfJkYUUAA0D8Lf//73cPrppzcKfXFhGD58eKPgt/vuu4cDDzwwLLjggrkrWYByJ9UgAQIECBAgQIAAAQIECBCYR8D+26RQADQHChOwABVGr2MCBAgQIECAAAECBAgQqJGA/XeNkp0RqicAzYNCBCxAhbDrlAABAgQIECBAgAABAgRqJmD/XbOE9xOuAqB5UIiABagQdp0SIECAAAECBAgQIECAQM0E7L9rlnAFQAkvk4AFqEzZMBYCBAgQIECAAAECBAgQSFXA/jvVzLYWlycAW/NydU4CFqCcIDVDgAABAgQIECBAgAABAgQyBOy/TY8ooABoHhQiYAEqhF2nBAgQIECAAAECBAgQIFAzAfvvmiW8n3AVAM2DQgQsQIWw65QAAQIECBAgQIAAAQIEaiZg/12zhCsASniZBCxAZcqGsRAgQIAAAQIECBAgQIBAqgL236lmtrW4PAHYmpercxKwAOUEqRkCBAgQIECAAAECBAgQIJAhYP9tekQBBUDzoBABC1Ah7DolQIAAAQIECBAgQIAAgZoJ2H/XLOH9hKsAaB4UImABKoRdpwQIECBAgAABAgQIECBQMwH775olXAFQwsskYAEqUzaMhQABAgQIECBAgAABAgRSFbD/TjWzrcXlCcDWvFydk4AFKCdIzRAgQIAAAQIECBAgQIAAgQwB+2/TIwooAJoHhQhYgAph1ykBAgQIECBAgAABAgQI1EzA/rtmCe8nXAVA86AQAQtQIew6JUCAAAECBAgQIECAAIGaCdh/1yzhCoASXiYBC1CZsmEsBAgQIECAAAECBAgQIJCqgP13qpltLS5PALbm5eqcBCxAOUFqhgABAgQIECBAgAABAgQIZAjYf5seUUAB0DwoRMACVAi7TgkQIECAAAECBAgQIECgZgL23zVLeD/hKgCaB4UIWIAKYdcpAQIECBAgQIAAAQIECNRMwP67ZglXAJTwMglYgMqUDWMhQIAAAQIECBAgQIAAgVQF7L9TzWxrcXkCsDUvV+ckMGXKlLDiiis2Wps0aVJ4z3vek1PLmiFAgAABAgQIECBAgAABAgRmCTz55JNh1KhRjf87efLksMIKK8CpoYACYA2TXoaQb7755tkLUBnGYwwECBAgQIAAAQIECBAgQCB1gfgAzoYbbph6mOLrQ0AB0LQoREABsBB2nRIgQIAAAQIECBAgQIBAjQUUAOubfAXA+ua+0MjffPPNcOeddzbGsNRSS4WhQ4cWOp5mOp/zsWnHlpsRc02ZBcznMmfH2NoRMKfbUXNPWQXM57JmxrjaFTCn25VzXxkFqjifp02bFp599tkG5zrrrBPmn3/+MtIaU5cFFAC7DKz5dAR8cWo6uRRJCOazWZCagDmdWkbrHY/5XO/8pxi9OZ1iVusbk/lc39xXPXIFwKpn0Ph7JmCh7xm1jnogYD73AFkXPRUwp3vKrbMuC5jPXQbWfM8FzOmek+uwiwLmcxdxNd1VAQXArvJqPCUBC31K2RSL+WwOpCZgTqeW0XrHYz7XO/8pRm9Op5jV+sZkPtc391WPXAGw6hk0/p4JWOh7Rq2jHgiYzz1A1kVPBczpnnLrrMsC5nOXgTXfcwFzuufkOuyigPncRVxNd1VAAbCrvBpPScBCn1I2xWI+mwOpCZjTqWW03vGYz/XOf4rRm9MpZrW+MZnP9c191SNXAKx6Bo2/ZwIW+p5R66gHAuZzD5B10VMBc7qn3DrrsoD53GVgzfdcwJzuObkOuyhgPncRV9NdFVAA7CqvxlMSsNCnlE2xmM/mQGoC5nRqGa13POZzvfOfYvTmdIpZrW9M5nN9c1/1yBUAq55B4++ZgIW+Z9Q66oGA+dwDZF30VMCc7im3zrosYD53GVjzPRcwp3tOrsMuCpjPXcTVdFcFFAC7yqtxAgQIECBAgAABAgQIECBAgAABAsUKKAAW6693AgQIECBAgAABAgQIECBAgAABAl0VUADsKq/GCRAgQIAAAQIECBAgQIAAAQIECBQroABYrL/eCRAgQIAAAQIECBAgQIAAAQIECHRVQAGwq7waJ0CAAAECBAgQIECAAAECBAgQIFCsgAJgsf56J0CAAAECBAgQIECAAAECBAgQINBVAQXArvJqnAABAgQIECBAgAABAgQIECBAgECxAgqAxfrrnQABAgQIECBAgAABAgQIECBAgEBXBRQAu8qrcQIECBAgQIAAAQIECBAgQIAAAQLFCigAFuuvdwIECBAgQIAAAQIECBAgQIAAAQJdFVAA7CqvxgkQIECAAAECBAgQIECAAAECBAgUK6AAWKy/3isi8Mgjj4QzzjgjXHrppSH+7/nmmy+sssoq4ROf+EQ44IADwoILLliRSAyzzgK33npruPzyy8N1110X/va3v4VnnnkmDBs2LCy77LJh9OjRYe+99w6bb755nYnEnoDAYYcdFk455ZTZkVx99dVhq622SiAyIdRF4LnnngvnnHNOuOiii8JDDz0UXnzxxfDud787vO997wtbbLFF2HXXXcMmm2xSFw5xVlzgrbfeCj/96U/DL3/5y3DHHXeEF154ofG7x3vf+96w6aabhn333TdsvPHGFY/S8KssEH8fnjRpUuM/N998c+M/zz//fCOkz33uc+Hcc89tKbz4u/ZZZ53VaO/ZZ58NSy21VBg1alRjru+4444tteViAnkLKADmLaq95ARi0W+PPfYIU6dO7TO21VZbLUycODGstNJKycUuoHQEttxyy3DttdcOGNCee+4Zzj777DB8+PABr3UBgbIJxM3lBhtsEKZNm6YAWLbkGE9TArFI8uUvf3n25rOvmz760Y+G3/zmN0215yICRQo8+uijYezYseHOO+/MHMbBBx8cTj311DBo0KAih6vvmgpkzbtWCoAzZ84M+++/f6P4198nFgHPPPNMc72mc60MYSsAliELxlBagbiZjE9Gvf7662HhhRcO48aNC1tvvXV44403wnnnnRd+9KMfNca++uqrN/5aFK/xIVBGgfjEanySJD7tt/vuuzee9Ft++eXD9OnTw4033tj4xfvxxx9vDP3Tn/50+PnPf17G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cz3mI/Daa681vkcqFkMeeOCBxpt+R4wY0XgKJX4fcTyF4EOgSgLxOy7/67/+q/F237vuuiu89NJLjadcl1lmmcYx9nhUOBa+W/09pUoGxlpugbwKgLOinDhxYqPId/PNNzeK3/HlTXGuxzV8p512KjeG0SUvoACYfIoFSIAAAQIECBAgQIAAAQIECBAgUGcBBcA6Z1/sBAgQIECAAAECBAgQIECAAAECyQsoACafYgESIECAAAECBAgQIECAAAECBAjUWUABsM7ZFzsBAgQIECBAgAABAgQIECBAgEDyAgqAyadYgAQIECBAgAABAgQIECBAgAABAnUWUACsc/bFToAAAQIECBAgQIAAAQIECBAgkLyAAmDyKRYgAQIECBAgQIAAAQIECBAgQIBAnQUUAOucfbETIECAAAECBAgQIECAAAECBAgkL6AAmHyKBUiAAAECBAgQIECAAAECBAgQIFBnAQXAOmdf7AQIECBAgAABAgQIECBAgAABAskLKAAmn2IBEiBAgAABAgQIECBAgAABAgQI1FlAAbDO2Rc7AQIECBAgQIAAAQIECBAgQIBA8gIKgMmnWIAECBAgQIAAAQIECBAgQIAAAQJ1FlAArHP2xU6AAAECBAgQIECAAAECBAgQIJC8gAJg8ikWIAECBAgQIECAAAECBAgQIECAQJ0FFADrnH2xEyBAgAABAgQIECBAgAABAgQIJC+gAJh8igVIgAABAgQIECBAgAABAgQIECBQZwEFwDpnX+wECBAgQIAAAQIECBAgQIAAAQLJCygAJp9iARIgQIAAAQIECBAgQIAAAQIECNRZQAGwztkXOwECBAgQIECAAAECBAgQIECAQPICCoDJp1iABAgQIECAAAECBAgQIECAAAECdRZQAKxz9sVOgAABAgQIECBAgAABAgQIECCQvIACYPIpFiABAgQIECBAgAABAgQIECBAgECdBRQA65x9sRMgQIAAAQIEMgR+8YtfhM985jONK/bdd9/wn//5n31e/cgjj4R11103TJ06NYwcOTLcdtttYaGFFmJLgAABAgQIECBQEgEFwJIkwjAIECBAgAABAmUU+OxnPxt+9rOfNYb2m9/8Jnz0ox+da5gzZswI22yzTbjmmmvC0KFDw5/+9Kew4YYbljEUYyJAgAABAgQI1FZAAbC2qRc4AQIECBAgQGBggZdffjmst956YcqUKWHJJZcMd955Z1hmmWVm3/jtb3/7/7d3/y5V9XEcwD96FQJJbBcUwVEI/MEVDeQu5h7aIhS0tjQZ4RIiOAnSHyA6Nbi61KCkBC35A3JQHBtbKiIxjXOG5+HheeCpb9f6en2d7V7O53s+39fnTm/OuSdmZmbKz3Nzc/HkyZP/X9QZBAgQIECAAAECv1VAAPhbuV2MAAECBAgQIHD5BLa2tmJsbCy+ffsWt2/fjvX19Whqaiof9a1Wq3FychKjo6OxsbERlUrl8m1QxwQIECBAgACBBhcQADb4gG2PAAECBAgQIFAPgdnZ2fIOv+JYWlqKBw8eRH9/fxwcHER7e3vs7u5Gd3d3PS5lDQIECBAgQIAAgToLCADrDGo5AgQIECBAgEAjCpyensbIyEi8efMmrl27Vt4JWPwnYHGsrq5G8V+BDgIECBAgQIAAgTwFBIB5zkVXBAgQIECAAIHsBI6OjuLmzZvx+fPnv3q7e/duFG8LdhAgQIAAAQIECOQrIADMdzY6I0CAAAECBAhkJ1A8Blw8DlwcxUtBDg8Po6OjI7s+NUSAAAECBAgQIPC3gADQr4EAAQIECBAgQOCHBD59+lS+Efj4+Lg8v3gRyMuXL6NWq/1QvZMIECBAgAABAgT+jIAA8M+4uyoBAgQIECBA4NIJ3L9/P5aXl8u+r1+/Hh8/fozOzs7Y29uLGzduXLr9aJgAAQIECBAgcFUEBIBXZdL2SYAAAQIECBD4BYG1tbW4c+dOucK9e/diamoqJiYmys+Tk5Px/PnzX1hdKQECBAgQIECAwEUKCAAvUtfaBAgQIECAAIEGEHj//n309fXFhw8foqenJ3Z2dso7AB8+fBjPnj0rd7iyshLT09MNsFtbIECAAAECBAg0noAAsPFmakcECBAgQIAAgboJnJ+fx/j4eLx48SIqlUq8evUqhoeHy/W/fPkSAwMD8e7du2hvby8fBe7q6qrbtS1EgAABAgQIECBQHwEBYH0crUKAAAECBAgQaEiBxcXFePToUbm34u2/T58+/cc+3759G9VqNU5OTuLWrVuxsbERzc3NDWlhUwQIECBAgACByyogALysk9M3AQIECBAgQOCCBfb392NwcDC+fv0aQ0NDsb29HS0tLf+66sLCQszMzJTfz8/Px+PHjy+4M8sTIECAAAECBAj8jIAA8Ge0nEuAAAECBAgQuCICRehXhH9FCNjW1hbFnX69vb3/ufuzs7Oo1WqxubkZra2t8fr16+jv778iUrZJgAABAgQIEMhfQACY/4x0SIAAAQIECBAgQIAAAQIECBAgQCBZQACYTKeQAAECBAgQIECAAAECBAgQIECAQP4CAsD8Z6RDAgQIECBAgAABAgQIECBAgAABAskCAsBkOoUECBAgQIAAAQIECBAgQIAAAQIE8hcQAOY/Ix0SIECAAAECBAgQIECAAAECBAgQSBYQACbTKSRAgAABAgQIECBAgAABAgQIECCQv4AAMP8Z6ZAAAQIECBAgQIAAAQIECBAgQIBAsoAAMJlOIQECBAgQIECAAAECBAgQIECAAIH8BQSA+c9IhwQIECBAgAABAgQIECBAgAABAgSSBQSAyXQKCRAgQIAAAQIECBAgQIAAAQIECOQvIADMf0Y6JECAAAECBAgQIECAAAECBAgQIJAsIABMplNIgAABAgQIECBAgAABAgQIECBAIH8BAWD+M9IhAQIECBAgQIAAAQIECBAgQIAAgWQBAWAynUICBAgQIECAAAECBAgQIECAAAEC+QsIAPOfkQ4JECBAgAABAgQIECBAgAABAgQIJAsIAJPpFBIgQIAAAQIECBAgQIAAAQIECBDIX0AAmP+MdEiAAAECBAgQIECAAAECBAgQIEAgWUAAmEynkAABAgQIECBAgAABAgQIECBAgED+AgLA/GekQwIECBAgQIAAAQIECBAgQIAAAQLJAgLAZDqFBAgQIECAAAECBAgQIECAAAECBPIXEADmPyMdEiBAgAABAgQIECBAgAABAgQIEEgWEAAm0ykkQIAAAQIECBAgQIAAAQIECBAgkL+AADD/GemQAAECBAgQIECAAAECBAgQIECAQLKAADCZTiEBAgQIECBAgAABAgQIECBAgACB/AUEgPnPSIcECBAgQIAAAQIECBAgQIAAAQIEkgW+A8VLoWJRweHEAAAAAElFTkSuQmCC\" width=\"640\">" - ], - "text/plain": [ - "<IPython.core.display.HTML object>" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "plt.plot(x,y)\n", "plt.xlabel(\"x\")\n", @@ -2226,7 +932,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b0da930d38ac497b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Now, instead of getting just an image in your web browser, you will get an interactive plot. At the bottom, there are some controls. If you click on the \"rectangle\" one, this will allow you to zoom in the plot by dragging a box around the feature you want to zoom in to. If you press the \"home\" button it will go back to the starting zoom. When you are done zooming around, you need to push the \"power\" button at the top right to finish zooming with the plot. If you want to start zooming again, you need to run the code again to make the plot.\n", "\n", @@ -2244,7 +959,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6761a172d5149df6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "If you have changed the `dpi` setting, you will need to re-run the command:" ] @@ -2260,7 +984,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-afe34d963cea156d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Plotting functions in Python\n", "\n", @@ -2288,7 +1021,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-67ab3373ae0fa8cc", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Now we want to calculate `y` for all of these x-points. Let's say we pick an angle of 45 degrees and an initial velocity $v_0$ of 10 m/s. We can then just directly use numpy \"vectorized\" calculations to calculate the values of `y` for all of our `x` points using a single line of code: " ] @@ -2310,7 +1052,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3103dddee2c9f829", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Now, let's plot it!" ] @@ -2330,7 +1081,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a1372d935bab78f8", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "We can also now see how easy it is to combine plotting functions with plotting data. For example, in with our voltage data above, if we want to plot a straight line function over over the data: " ] @@ -2351,14 +1111,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ecd3b09678cacd45", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Here, we also used the `linewidth` parameter (which can be shorted to `lw` to save typing) to make the line a bit fatter so it is easier to see, and used the '--' plot format specifier to make it a dashed line. " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-46dd5bd28954941b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.4:** Another way to get the exponent is to plot $\\log(V)$ vs $\\log(t)$ (a log-log plot). If the data really follows a power law relation, then this should give a straight line with a slope determined by the exponent. Try this, and then add a straight line $\\log(V) = p * \\log(t)$ to the plot. Play around with the value of $p$ until the line has the same slope as the data. For this plot, use solid filled circle for the data and a solid line for the theory.\n", "\n", @@ -2378,7 +1156,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8599a077e778277a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Fitting\n", "\n", @@ -2426,7 +1213,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3e41d51a52bac6e2", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "*(Here we also show some examples of more advanced formatting options of the <a href=https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.plot.html>plt.plot()</a> function to change the colors of the lines (`c=`) and the size of the data markers (`ms=`). I also give an example of adding a title to a graph: this can be handy in a notebook like this one, but in any real lab report, you should NEVER have titles on your plots. Instead, in a report, you should describe what is in the plot using a figure caption.)*\n", "\n", @@ -2439,7 +1235,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b352eec645cffa6c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.5:** Using the data you loaded in exercise 5.1, try fitting the function $V = at^p$ \"by hand\" to the data by manually adjusting $a$ and $p$ and replotting the function until you think you have the best fit. What are the values of $a$ and $p$ that you find? " ] @@ -2457,7 +1262,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-347f6460e621fd38", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Least squares fitting\n", "\n", @@ -2479,7 +1293,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8cd0ccee9956231a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "To use curve fit, we first need to create a Python function that returns the mathematical function that we want to fit. Our function is:\n", "\n", @@ -2502,7 +1325,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-318b791dd38d2a55", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In the simplest case (and it will always work when you are fitting a stright line), you can just then directly send the function and your x and y data and it will do the fit for you:" ] @@ -2518,7 +1350,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2ae5e791a8ee0965", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Tada! `curve_fit` actually returns two things. The first is an array containing the optimal fit values:" ] @@ -2534,7 +1375,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1a0084e54c4a46fa", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "The first one corresponds to the first parameter of the function and the second to the second: in our case, `a` and `b`:" ] @@ -2553,7 +1403,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-dd958e133a172472", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "We can now check what the fit looks like:" ] @@ -2574,7 +1433,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-efff4d4d08c4ef9a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Cool! Also, it turns out that the best statistical fit of the slope to our data is not exactly 2 but slightly higher (about 2.0148)\n", "\n", @@ -2583,7 +1451,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-728ba4c89dc5d7e0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.6:** Perform a least squares fit to of the data from exercise 5.1 to the function $V = at^p$. Make a plot of the function with the values of $p$ and $a$ from the fit on top of the data. How close are the fitted values to your attempt at fitting by hand? " ] @@ -2601,7 +1478,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-bd69b024d2582da3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Initial guesses\n", "The curve fit routine also has the option to provide initial guesses on your parameters using a keyword argument `p0`:" @@ -2621,7 +1507,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-da995318402bc3d7", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In this case it (nearly) did not make a difference: and in fact, for fitting a linear function, least squares fitting will converge to the same set of values, no matter what initial guess you provide. \n", "\n", @@ -2632,7 +1527,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7019fff9dd353955", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Errors on parameters\n", "\n", @@ -2655,7 +1559,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f0ffe772615a4907", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "The most important two for us are the diagonal elements: the first diagonal element tells us the square of the <a href=https://en.wikipedia.org/wiki/Standard_error>standard error</a> $\\sigma_a$ of parameter `a` and the second diagonal element gives us the square of $\\sigma_b$:" ] @@ -2674,7 +1587,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5d97036c0a1d934d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "(The standard error $\\sigma_a$ is also sometimes also written as $\\Delta a$, and is also sometimes called the parameter \"uncertainty\" instead of the parameter \"error\"). \n", "\n", @@ -2693,7 +1615,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b4032ef7311840a6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Here we used a placeholder format `%.2f` designating that we want to print as float with two decimals. There are a \n", "<a href=https://www.python-course.eu/python3_formatted_output.php>lot</a> of other ways to format your string.\n", @@ -2705,7 +1636,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d3c840f7897a22f3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.7** Calculated the errors on the fit parameters from your fit in exercise 5.5. Was your estimate of $p$ from exercise 5.4 within the statistical error margins from you fit? Were the values for $a$ and $p$ you found from \"fitting by hand\" within the statistical error margins? " ] @@ -2725,14 +1665,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7fe0b0fc78334b72", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Solutions to exercises" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2c686ad63c7718cb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.1:** " ] @@ -2741,19 +1699,36 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e723ee13a367dc4a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], "source": [ "# You can also use the option \"unpack=True\" to have loadtxt send back all columns separately\n", - "t2,v2 = np.loadtxt(\"exercise_data.dat\", unpack=True)\n", + "t2,v2 = np.loadtxt(\"resource/asnlib/public/exercise_data.dat\", unpack=True)\n", "\n", "print(\"Loaded\", len(v2), \"points\")" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f248bda277afb213", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.2:**" ] @@ -2762,6 +1737,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6698a19848ed5646", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -2775,7 +1758,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-16f1a60ee96ec552", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.3:** " ] @@ -2783,7 +1775,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-07471b53dc331edf", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "# 2 looks not bad\n", @@ -2797,7 +1798,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8c4f854c8b58688f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.4:** " ] @@ -2806,6 +1816,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d36fac25feffba52", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": false }, "outputs": [], @@ -2830,7 +1848,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b429d77d3941e298", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.5:** " ] @@ -2838,7 +1865,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-20348d9d40f824cb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "a=2\n", @@ -2852,7 +1888,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9e6875ad01124e78", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.6:** " ] @@ -2861,6 +1906,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f37090f982584db3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": false }, "outputs": [], @@ -2884,7 +1937,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-cd2f7a62303d00d3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.7**" ] @@ -2893,6 +1955,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c98f0fc3f9dfe50e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -2931,6 +2001,7 @@ } ], "metadata": { + "celltoolbar": "Create Assignment", "jupytext": { "formats": "ipynb,md" }, @@ -2949,7 +2020,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.5" }, "toc": { "base_numbering": "5", @@ -2967,7 +2038,7 @@ "width": "430.797px" }, "toc_section_display": true, - "toc_window_display": true + "toc_window_display": false } }, "nbformat": 4, diff --git a/Notebook 5/Notebook 5 Data in Python.md b/Notebook 5/Notebook 5 Data in Python.md index 85c4fb24769d6537522aa1ceed22e4fc6495e71d..a4db3d04db86ec4ef8ce7a257c056312adbbd1c5 100644 --- a/Notebook 5/Notebook 5 Data in Python.md +++ b/Notebook 5/Notebook 5 Data in Python.md @@ -5,19 +5,21 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: '1.1' - jupytext_version: 1.2.2 + format_version: '1.2' + jupytext_version: 1.3.0 kernelspec: display_name: Python 3 language: python name: python3 --- +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-e43ee4f9ec42e73b"} --> # Data in Python: Loading, Plotting, and Fitting In this notebook, we will explore how to load and save datafiles in Python using Numpy, and how to plot and explore data and functions with a library called Matplotlib. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-def642c582364cae"} --> **Learning objectives for this notebook:** * Student is able to load data from ASCII text files @@ -29,8 +31,9 @@ In this notebook, we will explore how to load and save datafiles in Python using * Student is able to fit a model to data using "fitting by hand" * Student is able to use `curve_fit` to perform a least-squares fit of a model to a dataset * Student is able to calculate the statistical error in the parameters of a least-squares fit +<!-- #endregion --> -<!-- #region --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f79c995cb536082e"} --> ## Loading and saving data with Numpy Here, we will explore some functions provided by numpy for loading data from files into python, and for saving data from python to files. @@ -78,59 +81,72 @@ https://docs.scipy.org/doc/numpy/reference/generated/numpy.loadtxt.html It can handle any type of delimiter: the default is any whitespace (tab or space), but this can also be configured using the `delimiter=` keyword argument. It also does not care about the file extension: for example, it is fine if a CSV file does not have a `.csv` extension (it doesn't even have to have an extension!). A typical convention is to name the files containing ASCII text data with an extension `.dat`. -Let's give it a try using the file `v_vs_time.dat`: +Let's give it a try using the file `v_vs_time.dat` in the folder `resource/asnlib/public/`: <!-- #endregion --> ```python import numpy as np -data = np.loadtxt("v_vs_time.dat") +data = np.loadtxt("resource/asnlib/public/v_vs_time.dat") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3aa9fb298e9a108a"} --> Here we have assigned the return value of `np.loadtxt` to a variable `data`, which is a numpy array. But what exactly is our variable `data`? We can find out more by looking at the shape of the returned `data` array: +<!-- #endregion --> ```python data.shape ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5bace50c3249b281"} --> When `np.loadtxt()` loads a file, it will return a 2D numpy array with of shape `(n,m)`, where `n` is the number lines in the file and `m` is the number of columns (here, we have 1000 points and 2 columns). As I mentioned above, the first column represents the time that the measurement was taken, and the second column represents the measured voltage in volts. We will typically want to extract these into two vectors `t` and `v`, which we can do using slicing: +<!-- #endregion --> ```python t = data[:,0] v = data[:,1] ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c63f7111a8fd6835"} --> We can look at the first 10 points and see that we have successfully loaded the data from the file! +<!-- #endregion --> ```python print(t[0:10]) print(v[0:10]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-9a33fae5f634b3cb"} --> We can check that the data is correct by opening the data file itself from the Jupyter file browser: Jupyter can also directly load and edit text files. (Another way of loading hand-recorded data into Python is just to use Jupyter to create an ASCII `.dat` file yourself.) +<!-- #endregion --> - -**Exercise 5.1:** Load the data from the file `exercise_data.dat`. The first column represents a measurement time in seconds, and the second represents a measurement voltage in volts. How many points are in the dataset? +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-def50c91314bd96b"} --> +**Exercise 5.1:** Load the data from the file `resource/asnlib/public/exercise_data.dat`. The first column represents a measurement time in seconds, and the second represents a measurement voltage in volts. How many points are in the dataset? *(Important: make sure that you use different variable names than t and v for the data you load, since we will continue to use the data we loaded in the example code in the sections below!)* +<!-- #endregion --> ```python your code here to load the data (using different variable names than t and v!) and check the size of the arrays you get ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-0793eb4c8f85801b"} --> ### Saving data We can also save data using a numpy routine `np.savetxt()`. To do this, we have to "pack" the data back into a numpy array of the correct shape. Let's take a look at an example where we calculate the square of the measured voltage and save this back into a new file: +<!-- #endregion --> ```python v_squared = v**2 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-fb66afd2a19403c5"} --> To "pack" them together into a matrix like the one returned by `np.loadtxt()`, we can first create a 2D matrix of the correct size and then use slicing with an assignment operator `=` to give the columns the correct values: +<!-- #endregion --> ```python # Create empty array @@ -141,12 +157,19 @@ to_save[:,0] = t to_save[:,1] = v_squared ``` -Now we are ready to use `np.savetxt()`: +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7823cff287a7691f"} --> +Now we are ready to use `np.savetxt()` in the current folder: +<!-- #endregion --> ```python np.savetxt("vsquare_vs_time.dat", to_save) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5c513d113d2f3e3f"} --> +If you go back to your workspace in a new tab, you will can see that this file has been created, and if you click on it, you can see what is inside it. +<!-- #endregion --> + +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-9fc0eeb9a99e75d9"} --> ## Plotting data and functions with Matplotlib In the above example, we loaded in some measured data from a file into an array. From there, we could perform mathematical operations on our data, and save it back to a new file. @@ -156,8 +179,9 @@ However, what every physicist will want to do is plot the data to see what it lo Here, we will show you how to do this using the `matplotlib` library: https://matplotlib.org +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-24bdaef0687142bb"} --> ### Plotting basics Specifically, we will need the `pyplot` module of `matplotlib`: @@ -165,28 +189,34 @@ Specifically, we will need the `pyplot` module of `matplotlib`: https://matplotlib.org/api/_as_gen/matplotlib.pyplot.html#module-matplotlib.pyplot We will import it with the shortcut / "name" / "prefix" `plt`: +<!-- #endregion --> ```python import matplotlib.pyplot as plt ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d879f0b0f2d9a70f"} --> I find it handy to also increase the "dpi" setting to make the plots a bit bigger. To do this, you need to execute the following in a separate cell from the `import` statement above: +<!-- #endregion --> ```python plt.rcParams['figure.dpi'] = 100 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-0373ada799aeca8e"} --> The routine for making line plots of your data is `plt.plot()`: https://matplotlib.org/api/_as_gen/matplotlib.pyplot.plot.html In its simplest form, you can just give it an array and ask it to plot it for you: +<!-- #endregion --> ```python plt.plot(v) plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2dad246fc48c5b7d"} --> Wow, looks really cool! The y axis is clearly the measured voltage. But what is the x-axis? @@ -203,6 +233,7 @@ Now that we know this, we should actually **ALWAYS add axis labels to our plots. **ALWAYS!!!** To do this, you can use the `plt.xlabel()` and `plt.ylabel()` commands: +<!-- #endregion --> ```python plt.plot(v) @@ -211,7 +242,9 @@ plt.ylabel("Voltage (V)") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-67a673593aba72e2"} --> If you want, you can also add a grid (depends on your personal taste preference and what you are using the plots for): +<!-- #endregion --> ```python plt.plot(v) @@ -221,7 +254,9 @@ plt.grid() plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-58b15e7163e2da55"} --> You can also add vertical or horizontal dashed lines, which I will use here to add zero axis lines: +<!-- #endregion --> ```python plt.plot(v) @@ -232,7 +267,9 @@ plt.axvline(0, ls=':', c='grey') plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-526802bd953d3531"} --> Ok, this is all great, but what if I want to plot the voltage vs. time, and not point number? For this, I give `plt.plot()` two arguments: +<!-- #endregion --> ```python plt.plot(t,v) @@ -241,9 +278,11 @@ plt.ylabel("Voltage (V)") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2f9e48b6a1d65039"} --> Great! This is nearly looking like something I could put into my lab report. But wait: how do I do this? Fortunately, `matplotlib` has functions that easily allow you to save your plot as a high-quality PDF format, perfect for importing into <a href=https://en.wikipedia.org/wiki/LaTeX>LaTeX</a> for writing your awesome lab report! +<!-- #endregion --> ```python plt.plot(t,v) @@ -253,9 +292,11 @@ plt.savefig("myplot.pdf") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ed9b147002abbbb6"} --> If you look in the file browser, you will now see a file `myplot.pdf`. If you open it, you will see that is a high-quality <a href=https://en.wikipedia.org/wiki/Vector_graphics>vector graphics</a> PDF. Sometimes, one may want to plot the data as points instead of a connected line. You can do this by adding a '.' after your arrays: +<!-- #endregion --> ```python plt.plot(t,v, '.') @@ -264,6 +305,7 @@ plt.ylabel("Voltage (V)") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6a6c5a44f576d0c0"} --> Do you want a different point size? Or a different symbol? You can see how to change all of this by looking at the documentation page of the plot function: https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.plot.html @@ -271,6 +313,7 @@ https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.plot.html You can also bring up the built-in help of the function by clicking on the function name in the code cell above and typing `Shift-Tab`. You may want to have more than one thing plotted in your plot. To do this, you simply have to run the plot command twice: +<!-- #endregion --> ```python # A "fake" dataset to illustrate plotting @@ -283,7 +326,9 @@ plt.ylabel("Voltage (V)") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1d9eda6969d17554"} --> Matplotlib will automatically change the color of the second dataset (you can also control them manually, see the <a href= https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.plot.html>documentation</a>). But it can be hard to tell which one is which, and for this reason, it can useful to add a legend to your plot, which can be done using this code: +<!-- #endregion --> ```python plt.plot(t,v, label="Voltage 1") @@ -294,7 +339,9 @@ plt.legend() plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a4e6050c6111cb50"} --> Finally, if you want a really big figure, you can also adjust the figure size: +<!-- #endregion --> ```python plt.figure(figsize=(12,8)) @@ -306,34 +353,40 @@ plt.legend() plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1f4975d1b1b7c3a2"} --> **Exercise 5.2:** Make a plot of the data you loaded from example 5.1. +<!-- #endregion --> ```python code to plot the data you loaded in example 5.1 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-93d67850489cb7df"} --> **Exercise 5.3:** As you may have noticed, the data from `example_data.dat` looks like a stright line, while the data from `exercise_data.dat` does not seem like straight line, but instead looks like a power law function $V = at^p$ with power $p$. One way to test for such power law relations is to make a plot with an x-axis that is not $t$ but instead $t^p$: if you get the correct $p$, then the data plotted will form a straight line. Write code to plot $V$ against $t^p$ and they put in different guesses at the power $p$ to see if you can see what the power is. +<!-- #endregion --> ```python code to plot data from example 5.1 with a different x-axis ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-78004ad6fb3f74dd"} --> ### Active zooming: The "notebook" driver The above examples are great for plotting your data for a report or exploring functions. Sometimes, though, you may want to be able to zoom in on a tiny little features in your function or data. Here is an example of some data that one might want to zoom in on: +<!-- #endregion --> ```python # unpack=True is also a handy way to load data # (saves some extra code) # Note this is a big data file, it will take a while to load. -x,y = np.loadtxt("example2.dat", unpack=True) +x,y = np.loadtxt("resource/asnlib/public/example2.dat", unpack=True) ``` ```python @@ -343,17 +396,21 @@ plt.ylabel("y") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ad73b12e0c6f6822"} --> Clearly, this function has some tiny little blip around x=2 and another around x=5. As a curious physicist, I'd like to zoom in and see in more detail what these look like. I could do this using the `plt.xlim()` and `plt.ylim()` functions, but what I'd really like to be able to do is to click and zoom in on them. For these, we can use `matplotlib` with a different driver called the `notebook` driver. To turn this on, we can use this command: +<!-- #endregion --> ```python %matplotlib notebook ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-34cc27094187c60d"} --> and then plot it again: +<!-- #endregion --> ```python plt.plot(x,y) @@ -362,20 +419,25 @@ plt.ylabel("y") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b0da930d38ac497b"} --> Now, instead of getting just an image in your web browser, you will get an interactive plot. At the bottom, there are some controls. If you click on the "rectangle" one, this will allow you to zoom in the plot by dragging a box around the feature you want to zoom in to. If you press the "home" button it will go back to the starting zoom. When you are done zooming around, you need to push the "power" button at the top right to finish zooming with the plot. If you want to start zooming again, you need to run the code again to make the plot. This can be quite useful, but can also be annoying (for example if you forget to turn the "power" button off). To turn off the `notebook` driver, use the `%matplotlib` command again with the option `inline`: +<!-- #endregion --> ```python %matplotlib inline ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6761a172d5149df6"} --> If you have changed the `dpi` setting, you will need to re-run the command: +<!-- #endregion --> ```python plt.rcParams['figure.dpi'] = 100 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-afe34d963cea156d"} --> ### Plotting functions in Python Sometimes (often?) in physics, we physicists will go through a long mathematical derivation to come up with a formula describing a physical problem. Sometimes this formula is very complicated, and it is then handy to be able to plot the formula to get feeling for what the function looks like. @@ -389,12 +451,15 @@ y = -\frac{g}{2 v_0^2 \cos^2 \theta} x^2 + x \tan \theta $$ The first step is to make a numpy array `x` that includes the points at which we want to evaluate the function. Let's guess and say we want to look in the range of x from 0 to 12 meters. We also need to pick the number of points: to get a smooth curve, let's pick say 1000 points: +<!-- #endregion --> ```python x = np.linspace(0,12,1000) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-67ab3373ae0fa8cc"} --> Now we want to calculate `y` for all of these x-points. Let's say we pick an angle of 45 degrees and an initial velocity $v_0$ of 10 m/s. We can then just directly use numpy "vectorized" calculations to calculate the values of `y` for all of our `x` points using a single line of code: +<!-- #endregion --> ```python # Parameters @@ -406,7 +471,9 @@ g = 9.8 # m/s^2 y = -g/(2*v0**2*np.cos(theta)**2)*x**2 + x*np.tan(theta) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3103dddee2c9f829"} --> Now, let's plot it! +<!-- #endregion --> ```python plt.plot(x,y) @@ -416,7 +483,9 @@ plt.axhline(0, ls=":", c="grey") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a1372d935bab78f8"} --> We can also now see how easy it is to combine plotting functions with plotting data. For example, in with our voltage data above, if we want to plot a straight line function over over the data: +<!-- #endregion --> ```python line = 2*t @@ -427,17 +496,21 @@ plt.ylabel("Voltage (V)") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ecd3b09678cacd45"} --> Here, we also used the `linewidth` parameter (which can be shorted to `lw` to save typing) to make the line a bit fatter so it is easier to see, and used the '--' plot format specifier to make it a dashed line. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-46dd5bd28954941b"} --> **Exercise 5.4:** Another way to get the exponent is to plot $\log(V)$ vs $\log(t)$ (a log-log plot). If the data really follows a power law relation, then this should give a straight line with a slope determined by the exponent. Try this, and then add a straight line $\log(V) = p * \log(t)$ to the plot. Play around with the value of $p$ until the line has the same slope as the data. For this plot, use solid filled circle for the data and a solid line for the theory. *(Do you get an warning message? Why?)* +<!-- #endregion --> ```python your code ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8599a077e778277a"} --> ## Fitting In experimental physics, one of our primary goals is to try to figure out if our observed experimental measurements match the theoretical predictions that we can make from mathematical models. @@ -462,6 +535,7 @@ where $a$ is some parameter with the units of V/s representing the slope of the The fact that the the line $V = 2t$ (the orange dashed line) seems to pass through the middle of our blue data already suggests that for our data (the blue points), $a = 2$ V/s and $b = 0$ V seems like pretty good parameters for our model to describe the data. We can also see that `a = 1.7` and `b = 1.7` are also OK (they go through a lot of the data points), but are probably not as good: +<!-- #endregion --> ```python line1 = 1.7*t+1.7 @@ -476,6 +550,7 @@ plt.title("a = 1.7 and b = 1.7 are also OK, but not as good as a = 2 and b = 0") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3e41d51a52bac6e2"} --> *(Here we also show some examples of more advanced formatting options of the <a href=https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.plot.html>plt.plot()</a> function to change the colors of the lines (`c=`) and the size of the data markers (`ms=`). I also give an example of adding a title to a graph: this can be handy in a notebook like this one, but in any real lab report, you should NEVER have titles on your plots. Instead, in a report, you should describe what is in the plot using a figure caption.)* From "fitting by hand" (just plotting the data and adjusting the parameters), you can already get a good feeling for functions and parameters fit "pretty well" and which "not as well". @@ -483,14 +558,17 @@ From "fitting by hand" (just plotting the data and adjusting the parameters), yo But: how do I find the BEST parameters (`a` and `b` in this case) for my data? And how do I know how accurately my data allows me to determine these parameters? For that, we can use a procedure called <a href=https://en.wikipedia.org/wiki/Least_squares>least squares fitting</a>. In the next part of the lab course, you will learn more about least squares fitting and how it works. Here, we will look at how to code it in Python, how to use it to get the best fit parameters of a model for you data, and how to find out what the <a href=https://en.wikipedia.org/wiki/Errors_and_residuals>statistical error</a> is on the parameters of your model given the data you are using for the fit. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b352eec645cffa6c"} --> **Exercise 5.5:** Using the data you loaded in exercise 5.1, try fitting the function $V = at^p$ "by hand" to the data by manually adjusting $a$ and $p$ and replotting the function until you think you have the best fit. What are the values of $a$ and $p$ that you find? +<!-- #endregion --> ```python code to fit data "by hand" to a power law ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-347f6460e621fd38"} --> ### Least squares fitting To have Python automatically find the best parameters for fitting your data with a given function, you can use the `curve_fit()` routine from the `scipy` (scientific Python) package: @@ -498,11 +576,13 @@ To have Python automatically find the best parameters for fitting your data with https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html Since we will use only the `curve_fit` routine, we will import it on its own: +<!-- #endregion --> ```python from scipy.optimize import curve_fit ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8cd0ccee9956231a"} --> To use curve fit, we first need to create a Python function that returns the mathematical function that we want to fit. Our function is: $$ @@ -510,25 +590,32 @@ f(x) = ax + b $$ It has one variable, $x$, and two parameters, $a$ and $b$. For using `curve_fit`, we will need to make a Python function that takes three arguments: the first is the variable `x` (for the above, it will be time), and the other two arguments are the fit parameters `a` and `b`, and which should return the predicted voltage: +<!-- #endregion --> ```python def f(x,a,b): return a*x+b ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-318b791dd38d2a55"} --> In the simplest case (and it will always work when you are fitting a stright line), you can just then directly send the function and your x and y data and it will do the fit for you: +<!-- #endregion --> ```python values, covariance = curve_fit(f, t, v) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2ae5e791a8ee0965"} --> Tada! `curve_fit` actually returns two things. The first is an array containing the optimal fit values: +<!-- #endregion --> ```python print(values) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1a0084e54c4a46fa"} --> The first one corresponds to the first parameter of the function and the second to the second: in our case, `a` and `b`: +<!-- #endregion --> ```python a_fit = values[0] @@ -537,7 +624,9 @@ print(a_fit) print(b_fit) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-dd958e133a172472"} --> We can now check what the fit looks like: +<!-- #endregion --> ```python plt.plot(t,v, '.',c='k', ms=2) @@ -548,19 +637,24 @@ plt.ylabel("Voltage (V)") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-efff4d4d08c4ef9a"} --> Cool! Also, it turns out that the best statistical fit of the slope to our data is not exactly 2 but slightly higher (about 2.0148) In addition, sometimes in measurements, we also have error bars on the each of the individual black data points in the plot, and sometimes some data points could have more error than others. In these cases, the fitting routine can take these individual error bars into account when performing this fit: we will see more about this later in the course. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-728ba4c89dc5d7e0"} --> **Exercise 5.6:** Perform a least squares fit to of the data from exercise 5.1 to the function $V = at^p$. Make a plot of the function with the values of $p$ and $a$ from the fit on top of the data. How close are the fitted values to your attempt at fitting by hand? +<!-- #endregion --> ```python code that performs a least square fit to the exercise data ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-bd69b024d2582da3"} --> ### Initial guesses The curve fit routine also has the option to provide initial guesses on your parameters using a keyword argument `p0`: +<!-- #endregion --> ```python initial_guesses = (2.5,1) @@ -569,13 +663,15 @@ print(values2) print(values) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-da995318402bc3d7"} --> In this case it (nearly) did not make a difference: and in fact, for fitting a linear function, least squares fitting will converge to the same set of values, no matter what initial guess you provide. This is NOT true for functions $f(x)$ that are not linear in $x$. In really bad cases, the parameter values that `curve_fit` finds can even be highly sensitive to the initial conditions. For this reason, it is always a good idea, and sometimes absolutely necessary, to first do some "fitting by hand", and then ideally provide these initial conditions to the `curve_fit` routine. And even if you do not provide hand-tuned initial guesses (which you may not if you are automatically fitting a huge number of datasets), it is important that you always make a plot of the the fitted curves that you find on top of the data just to make sure nothing goes wrong. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7019fff9dd353955"} --> ### Errors on parameters We found above that the best statistical fit to our data was not our initial estimate of `a = 2`, but actually more like `a = 2.0148`. @@ -583,13 +679,16 @@ We found above that the best statistical fit to our data was not our initial est But the line with `a = 2` also looked pretty good, right? How much better is the fit with `a = 2.0148`? To find this out, we can use the other variable that `curve_fit` returned: the <a href=https://en.wikipedia.org/wiki/Covariance_matrix>covariance matrix</a>. With two parameters, the covariance matrix is a 2x2 array: +<!-- #endregion --> ```python values, covariance = curve_fit(f, t, v) print(covariance) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f0ffe772615a4907"} --> The most important two for us are the diagonal elements: the first diagonal element tells us the square of the <a href=https://en.wikipedia.org/wiki/Standard_error>standard error</a> $\sigma_a$ of parameter `a` and the second diagonal element gives us the square of $\sigma_b$: +<!-- #endregion --> ```python a_err = np.sqrt(covariance[0,0]) @@ -598,24 +697,29 @@ print(a_err) print(b_err) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5d97036c0a1d934d"} --> (The standard error $\sigma_a$ is also sometimes also written as $\Delta a$, and is also sometimes called the parameter "uncertainty" instead of the parameter "error"). We can now quote the full values of the parameters from our fit, including errors: +<!-- #endregion --> ```python print("The value of a is %.2f +/- %.2f" % (a_fit, a_err)) print("The value of b is %.2f +/- %.2f" % (b_fit, b_err)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b4032ef7311840a6"} --> Here we used a placeholder format `%.2f` designating that we want to print as float with two decimals. There are a <a href=https://www.python-course.eu/python3_formatted_output.php>lot</a> of other ways to format your string. Note that typically when you quote an error, you should pick only one significant digit. And also, when you quote the value of a parameter, you should also quote it with the same number of decimal places as your error. So in this case, I quote the value of `a` as 2.015, and not its "exact" value of 2.0148415283012246. Why? The reason is that doesn't make sense to quote more digits on the value of `a` than a number of digits corresponding to the size of the statistical error, and so physicists and scientists truncate the accuracy of the numbers they report to a level comparable to the error. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d3c840f7897a22f3"} --> **Exercise 5.7** Calculated the errors on the fit parameters from your fit in exercise 5.5. Was your estimate of $p$ from exercise 5.4 within the statistical error margins from you fit? Were the values for $a$ and $p$ you found from "fitting by hand" within the statistical error margins? +<!-- #endregion --> ```python code to calculate the parameter uncertainties (errors) and check if your earlier @@ -623,21 +727,26 @@ estimates agree with the least-square-fit values to within the error margins of the fit ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7fe0b0fc78334b72"} --> ## Solutions to exercises +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2c686ad63c7718cb"} --> **Exercise 5.1:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-e723ee13a367dc4a"} # You can also use the option "unpack=True" to have loadtxt send back all columns separately -t2,v2 = np.loadtxt("exercise_data.dat", unpack=True) +t2,v2 = np.loadtxt("resource/asnlib/public/exercise_data.dat", unpack=True) print("Loaded", len(v2), "points") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f248bda277afb213"} --> **Exercise 5.2:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6698a19848ed5646"} plt.plot(t2,v2) # ALL PLOTS MUST HAVE LABELS WITH UNITS!!!! plt.xlabel("t (s)") @@ -645,9 +754,11 @@ plt.ylabel("v (V)") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-16f1a60ee96ec552"} --> **Exercise 5.3:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-07471b53dc331edf"} # 2 looks not bad p = 2 plt.plot(t2**p,v2) @@ -657,9 +768,11 @@ plt.ylabel("v (V)") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8c4f854c8b58688f"} --> **Exercise 5.4:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d36fac25feffba52"} # 2 looks not bad plt.plot(np.log(t2),np.log(v2),'o', label="Data") @@ -678,9 +791,11 @@ plt.ylabel("log(v) (log(V))") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b429d77d3941e298"} --> **Exercise 5.5:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-20348d9d40f824cb"} a=2 p=2.34 plt.plot(t2,v2, 'o', label='Data') @@ -690,9 +805,11 @@ plt.xlabel("t (s)") plt.ylabel("v (V)") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-9e6875ad01124e78"} --> **Exercise 5.6:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f37090f982584db3"} def f2(t,a,p): return a*t**p @@ -710,9 +827,11 @@ plt.xlabel("t (s)") plt.ylabel("v (V)") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-cd2f7a62303d00d3"} --> **Exercise 5.7** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c98f0fc3f9dfe50e"} a_err = np.sqrt(covariance[0,0]) p_err = np.sqrt(covariance[1,1]) diff --git a/Notebook 5/example2.dat b/Notebook 5/resource/asnlib/public/example2.dat similarity index 100% rename from Notebook 5/example2.dat rename to Notebook 5/resource/asnlib/public/example2.dat diff --git a/Notebook 5/exercise_data.dat b/Notebook 5/resource/asnlib/public/exercise_data.dat similarity index 100% rename from Notebook 5/exercise_data.dat rename to Notebook 5/resource/asnlib/public/exercise_data.dat diff --git a/Notebook 5/generate data.ipynb b/Notebook 5/resource/asnlib/public/generate data.ipynb similarity index 100% rename from Notebook 5/generate data.ipynb rename to Notebook 5/resource/asnlib/public/generate data.ipynb diff --git a/Notebook 5/generate data.md b/Notebook 5/resource/asnlib/public/generate data.md similarity index 100% rename from Notebook 5/generate data.md rename to Notebook 5/resource/asnlib/public/generate data.md diff --git a/Notebook 5/v_vs_time.dat b/Notebook 5/resource/asnlib/public/v_vs_time.dat similarity index 100% rename from Notebook 5/v_vs_time.dat rename to Notebook 5/resource/asnlib/public/v_vs_time.dat diff --git a/deployment_scripts_vocareum/pull_Additional_Material.sh b/deployment_scripts_vocareum/pull_Additional_Material.sh new file mode 100644 index 0000000000000000000000000000000000000000..1d5418c67effc980912e51a0553698e672b5982f --- /dev/null +++ b/deployment_scripts_vocareum/pull_Additional_Material.sh @@ -0,0 +1,7 @@ +ln -s ../resource . +wget -O 'Additional Programming Concepts in Python.ipynb' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Additional%20Material/Additional%20Programming%20Concepts%20in%20Python.ipynb +cd ../resource/asnlib/public +wget -O 'call_by_reference.png' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Additional%20Material/resource/asnlib/public/call_by_reference.png +wget -O 'call_by_value.png' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Additional%20Material/resource/asnlib/public/call_by_value.png +wget -O 'big_error_1.png' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Additional%20Material/resource/asnlib/public/big_error_1.png +wget -O 'big_error_2.png' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Additional%20Material/resource/asnlib/public/big_error_2.png diff --git a/deployment_scripts_vocareum/pull_Good_Programming_Practices.sh b/deployment_scripts_vocareum/pull_Good_Programming_Practices.sh new file mode 100644 index 0000000000000000000000000000000000000000..434c55256985b1f9cdb7ada0e07a569973992ba2 --- /dev/null +++ b/deployment_scripts_vocareum/pull_Good_Programming_Practices.sh @@ -0,0 +1,3 @@ +ln -s ../resource . +wget -O 'Good coding practices.ipynb' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Good%20Programming%20Practices/Good%20coding%20practices.ipynb +cd ../resource/asnlib/public diff --git a/deployment_scripts_vocareum/pull_Notebook_1.sh b/deployment_scripts_vocareum/pull_Notebook_1.sh new file mode 100644 index 0000000000000000000000000000000000000000..5effeffe43e8791242fc4553c25780ab0d3d359e --- /dev/null +++ b/deployment_scripts_vocareum/pull_Notebook_1.sh @@ -0,0 +1,7 @@ +ln -s ../resource . +wget -O 'Notebook 1 Python Basics.ipynb' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Notebook%201/Notebook%201%20Python%20Basics.ipynb +cd ../resource/asnlib/public +wget -O 'Notebook_1_anatomy_of_an_error.png' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Notebook%201/resource/asnlib/public/Notebook_1_anatomy_of_an_error.png +wget -O 'Notebook_1_behind_the_scenes.png' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Notebook%201/resource/asnlib/public/Notebook_1_behind_the_scenes.png +wget -O 'Notebook_1_stop_button.png' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Notebook%201/resource/asnlib/public/Notebook_1_stop_button.png +wget -O 'Notebook_1_restartkernelmenu.png' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Notebook%201/resource/asnlib/public/Notebook_1_restartkernelmenu.png diff --git a/deployment_scripts_vocareum/pull_Notebook_2.sh b/deployment_scripts_vocareum/pull_Notebook_2.sh new file mode 100644 index 0000000000000000000000000000000000000000..3414671c55447bd026a4e87fa80a6baaf2ba4c20 --- /dev/null +++ b/deployment_scripts_vocareum/pull_Notebook_2.sh @@ -0,0 +1,3 @@ +ln -s ../resource . +wget -O 'Notebook 2 Functions.ipynb' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Notebook%202/Notebook%202%20Functions.ipynb +cd ../resource/asnlib/public diff --git a/deployment_scripts_vocareum/pull_Notebook_3.sh b/deployment_scripts_vocareum/pull_Notebook_3.sh new file mode 100644 index 0000000000000000000000000000000000000000..bc214661b247561073381d437753a2dade38991b --- /dev/null +++ b/deployment_scripts_vocareum/pull_Notebook_3.sh @@ -0,0 +1,3 @@ +ln -s ../resource . +wget -O 'Notebook 3 Program Flow Control.ipynb' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Notebook%203/Notebook%203%20Program%20Flow%20Control.ipynb +cd ../resource/asnlib/public diff --git a/deployment_scripts_vocareum/pull_Notebook_4.sh b/deployment_scripts_vocareum/pull_Notebook_4.sh new file mode 100644 index 0000000000000000000000000000000000000000..d9e231c52610425dbf900b3cdc3a2de0feadc44d --- /dev/null +++ b/deployment_scripts_vocareum/pull_Notebook_4.sh @@ -0,0 +1,3 @@ +ln -s ../resource . +wget -O 'Notebook 4 Scientific Computing with Numpy.ipynb' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Notebook%204/Notebook%204%20Scientific%20Computing%20with%20Numpy.ipynb +cd ../resource/asnlib/public diff --git a/deployment_scripts_vocareum/pull_Notebook_5.sh b/deployment_scripts_vocareum/pull_Notebook_5.sh new file mode 100644 index 0000000000000000000000000000000000000000..163f1e6c9c65e998e6164131ed9cf2948920e949 --- /dev/null +++ b/deployment_scripts_vocareum/pull_Notebook_5.sh @@ -0,0 +1,6 @@ +ln -s ../resource . +wget -O 'Notebook 5 Data in Python.ipynb' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Notebook%205/Notebook%205%20Data%20in%20Python.ipynb +cd ../resource/asnlib/public +wget -O 'v_vs_time.dat' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Notebook%205/resource/asnlib/public/v_vs_time.dat +wget -O 'exercise_data.dat' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Notebook%205/resource/asnlib/public/exercise_data.dat +wget -O 'example2.dat' https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/Notebook%205/resource/asnlib/public/example2.dat diff --git a/deployment_scripts_vocareum/update_deployment_scripts.ipynb b/deployment_scripts_vocareum/update_deployment_scripts.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..8f6567a185ecebe74eb47e1466fb8ae12adf906e --- /dev/null +++ b/deployment_scripts_vocareum/update_deployment_scripts.ipynb @@ -0,0 +1,134 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2020-08-20T20:02:05.285878Z", + "start_time": "2020-08-20T20:02:05.281550Z" + } + }, + "outputs": [], + "source": [ + "import glob" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "OK, so lacking a decent interface for deploying to Vocareum, I will generated some shell scripts here that I can upload to Vocareum to use wget to pull the material in. " + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "ExecuteTime": { + "end_time": "2020-08-20T20:04:46.566381Z", + "start_time": "2020-08-20T20:04:46.560523Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['../Additional Material/',\n", + " '../Good Programming Practices/',\n", + " '../Notebook 1/',\n", + " '../Notebook 2/',\n", + " '../Notebook 3/',\n", + " '../Notebook 4/',\n", + " '../Notebook 5/']" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "to_deploy = sorted([s for s in glob.glob(\"../*/\") if \"Course\" not in s and \"scripts\" not in s])\n", + "to_deploy" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": { + "ExecuteTime": { + "end_time": "2020-08-20T20:44:43.235998Z", + "start_time": "2020-08-20T20:44:43.211915Z" + } + }, + "outputs": [], + "source": [ + "base_url = \"https://gitlab.tudelft.nl/python-for-applied-physics/practicum-lecture-notes/-/raw/master/\"\n", + "\n", + "def get_command(file):\n", + " basename = file.split(\"/\")[-1]\n", + " url = base_url + file[3:]\n", + " url = url.replace(\" \", \"%20\")\n", + " return \"wget -O '%s' %s \\n\" % (basename, url)\n", + "\n", + "for folder in to_deploy:\n", + " script_name = \"pull_\" + folder[3:-1].replace(\" \", \"_\") + \".sh\"\n", + " # First the notebooks \n", + " # just in case it's not there yet..to enable viewing images while in instructor mode\n", + " script = \"ln -s ../resource . \\n\" \n", + " notebook = glob.glob(folder+\"*ipynb\")[0] # only one notebook per folder...\n", + " script += get_command(notebook)\n", + " script += \"cd ../resource/asnlib/public\\n\"\n", + " files = []\n", + " files += glob.glob(folder+\"resource/asnlib/public/*.png\")\n", + " files += glob.glob(folder+\"resource/asnlib/public/*.dat\")\n", + " for file in files:\n", + " script += get_command(file)\n", + " with open(script_name, \"w\") as f:\n", + " f.write(script)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "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.7.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": false, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}