Skip to content
Snippets Groups Projects
Notebook 4 Scientific Computing with Numpy.ipynb 53.8 KiB
Newer Older
Gary Steele's avatar
Gary Steele committed
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-33372ef05bccfcb0",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "# Scientific Computing in Python with Numpy \n",
    "\n",
    "<a href=https://numpy.org/>Numpy</a> (numerical Python) is a library designed for performing scientific computing in Python. \n",
    "\n",
    "In this notebook, we will introduce numpy arrays, a data structure introduced in numpy for working with vectors and matrices. We will explore how to create them, how to manipulate them, and how to use them for efficient numerical calculations using numpy functions. \n",
    "\n",
    "**Learning objectives for this notebook:**\n",
    "\n",
    "* Student is able to create (multidimensional) numpy arrays from a list of numbers\n",
    "* Student is able to use indexing and slicing with (multidimensional) numpy arrays\n",
    "* Student is able to iterate over a numpy array \n",
    "* Student is able to perform mathematical operations on numpy arrays\n",
    "* Student is able to use functions for creating arrays (eg. `np.zeros()`, `np.linspace()`, `np.random.random()`\n",
    "* Student is able to use numpy functions for vectorized calculations\n",
    "* Student is able to demonstrate the speed increase of vectorized calculations using `time()`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-7d7b639cd03ba916",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "## Numpy Arrays\n",
    "\n",
    "Until now, the variable types we have been working with in Python represent relatively simple data types:\n",
    "\n",
    "* `int`: integer numbers\n",
    "* `float`: floating point numbers\n",
    "* `complex`: complex-valued floating point numbers\n",
    "* `bool`: boolean \"truth\" values (which can have `True` and `False`)\n",
    "* `str`: strings \n",
    "* `list`: list of variables\n",
    "\n",
    "The first four are really actually very simple data types, but actually the last one is more complicated than it looks (see the extra notebook for more details if your interested...). The `list` is a vector like-variable type. However, unlike physical vectors, it cannot be multiplied, subtracted, etc. If you are interested in  lists, you can read more about them in the additional material 2.3.  \n",
    "\n",
    "Here, we will introduce a new datatype that is handy for Physics calculations and that comes from the Python software package <a href=https://numpy.org>numpy</a> called **numpy arrays**.\n",
    "\n",
    "What are numpy arrays?  \n",
    "\n",
    "Numpy arrays are a way to work in Python with not just single numbers, but a whole bunch of numbers. With numpy arrays these numbers can be manipulated just like you do in, for example, linear algebra when one works with vectors:\n",
    "\n",
    "https://en.wikipedia.org/wiki/Row_and_column_vectors\n",
    "\n",
    "For example, a (column) vector $\\vec{a}$ with 5 entries might look like this:\n",
    "\n",
    "$$\n",
    "\\vec{a} = \\begin{bmatrix}\n",
    "1 \\\\\n",
    "2 \\\\\n",
    "3 \\\\\n",
    "4 \\\\\n",
    "5\n",
    "\\end{bmatrix}\n",
    "$$\n",
    "\n",
    "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). \n",
    "\n",
    "To use numpy arrays, we first need to import the numpy library, which we will do using the shortened name \"np\" (to save typing):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
Gary Steele's avatar
Gary Steele committed
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-4cd897115e9a55da",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    },
Gary Steele's avatar
Gary Steele committed
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-2b8a256ccf823e14",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "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:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = np.array([1,2,3,4,5])\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-cfdb9edf4b2e1707",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "Note  that numpy does not make a distinction between row vectors and column vectors: there are just vectors. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-aa801acee39c518f",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "### Indexing arrays (and counting from zero)\n",
    "\n",
    "One useful thing about arrays is that you can access the elements of the array using square brackets:\n",
    "\n",
    "`a[n]` will give you the n-th element of the array `a`. \n",
    "\n",
    "This process of extracting a single element from the array is called **indexing**. \n",
    "\n",
    "Note that here we encounter for the fist time what is know as the python **counting from zero** convention. What is the counting from zero convention? In the example above, we created a array:\n",
    "\n",
    "```\n",
    "a = np.array([1,2,3,4,5])\n",
    "```\n",
    "\n",
    "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?\n",
    "\n",
    "Let's try it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(a[1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-992a7aa5df6186e4",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "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",
    "(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.)\n",
    "\n",
    "This also helps better understand the `range()` function: for example, to loop over all the elements in `a`, I can use this code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for i in range(len(a)):\n",
    "    n = a[i]\n",
    "    print('a[%d] is %d' % (i,n))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-ab7bf8fa5a2ac10f",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "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:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for n in a:\n",
    "    print(n)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-e1299e2159378fdb",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "In Python, if you try to index beyond the end of the array, you will get an error: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "a[5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-77aff5e6ccdff810",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "(Remember: indexing starts at zero!)\n",
    "\n",
    "Python also has a handy feature: negative indices count backwards from the end, and index `-1` corresponds to the last element in the array! "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "a[-1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "a[-2]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-8398fd216f6b2f58",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "We can also use indexing to change the values of elements in our array:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(a)\n",
    "a[2] = -1\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-3dae75f8494e44f3",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "**Exercise 4.1** Set the first three, and the last two, entries of the following array to zero:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "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])\n",
    "\n",
    "#some code to set the first three and last two entries to zero \n",
    "\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-3b080cba952a146f",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "### Slicing numpy arrays\n",
    "\n",
    "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. \n",
    "\n",
    "To do this, I put not just a single number inside my square brackets, but instead two numbers, separated by a colon `:`\n",
    "\n",
    "`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`. \n",
    "\n",
    "Let's look at a concrete example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "a = np.array(range(10))\n",
    "print(a)\n",
    "print(a[0:5])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-70abffff9b88cf27",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "The notation `a[0:5]` has \"sliced\" out the first five elements of the array.\n",
    "\n",
    "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):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "print(a[:5])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "print(a[5:])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-a8b2ffcd10cfc7b1",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "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:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "print(a[0:10:2])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-24b9d17391ceeb7e",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "Fun: you can also use negative steps:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "print(a[-1:-11:-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-6ee08109e60cb51f",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "And finally, unlike indexing, Python is a bit lenient if you slice off the end of an array:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "print(a[0:20])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-8d1518c5eafa34e8",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "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."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "a = np.array(range(20))+1\n",
    "print(a)\n",
    "some code that sets the first 10 entries to zero\n",
Gary Steele's avatar
Gary Steele committed
    "print(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-9b377accb5c0210c",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "### Mathematical operations on arrays\n",
    "\n",
    "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:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = np.array([1,2,3,4,5])\n",
    "print(2*a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(a+a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "print(a+1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "print(a-a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "print(a/2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(a**2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-2963828a5132a24a",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "What about if I multiply two vectors together? \n",
    "\n",
    "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:\n",
    "\n",
    "https://en.wikipedia.org/wiki/Row_and_column_vectors#Operations\n",
    "\n",
    "The \"dot product\" corresponds to multiplying a column vector by a row vector to produce a single number. The \"outer product\" (also called the \"tensor product\") corresponds to multiplying the column vector by the row vector to make a matrix. \n",
    "\n",
    "**Question:** If I type `a*a`, or more generally `a*b`, does Python use the inner or outer product? \n",
    "\n",
    "It turns out: it uses **neither!** In Python, the notation `a*a` produces what is commonly called the \"element-wise\" product: specifically,\n",
    "\n",
    "`a*b = [a[0]*b[0] a[1]*b[1] a[2]*b[2] ...]`\n",
    "\n",
    "(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...)\n",
    "\n",
    "We can see this in action here:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(a*a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-713408533e01c84c",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "What if I actually want the dot product or the outer product? For that, Python has functions `np.dot()` and `np.outer()`: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(np.dot(a,a))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(np.outer(a,a))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-d29f4ba63452bfee",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "Pretty much all operators work with numpy arrays, even comparison operators, which can sometimes be very handy:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(a)\n",
    "print(a>3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-8657218bbd2eabbc",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "**Exercise 4.3:** Generate a sequence of the first 20 powers of 2 in a numpy array (starting at $2^0$). \n",
    "\n",
    "Your output should be an array $[2^0, 2^1, 2^2, 2^3, ...]$. \n",
    "\n",
    "*(Hint: Start with a numpy array created using an appropriate range function that makes an array [0,1,2,3,...])*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
Gary Steele's avatar
Gary Steele committed
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-809eedfdadcd8b57",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "## Functions for creating numpy arrays\n",
    "\n",
    "In numpy, there are also several handy functions for automatically creating arrays:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = np.zeros(10)\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = np.ones(10)\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-a3a728bdca7ae5e3",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "### `np.linspace`\n",
    "\n",
    "To automatically generate an array with linerly increasing values you can take `np.linspace()`:\n",
    "\n",
    "https://docs.scipy.org/doc/numpy/reference/generated/numpy.linspace.html\n",
    "\n",
    "It takes three arguments: the starting number, the ending number, and the number of points.\n",
    "\n",
    "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:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = np.linspace(0,20,40)\n",
    "print(a)\n",
    "print()\n",
    "print(\"Length is: \", len(a))\n",
    "print(\"Step size is: \", a[1]-a[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-ea30686a4490a44d",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "Note that if we wanted to have a step size of exactly 0.5, we need a total of 41 points:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = np.linspace(0,20,41)\n",
    "print(a)\n",
    "print()\n",
    "print(\"Length is: \", len(a))\n",
    "print(\"Step size is: \", a[1]-a[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-60d973103e7a78a9",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "**Exercise 4.4:** Generate an array that runs from -2 to 1 with 20 points using `linspace`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "a = your code\n",
    "print(a)"
Gary Steele's avatar
Gary Steele committed
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-7c4a427b963a9b3c",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "source": [
    "### `np.arange()`\n",
    "\n",
    "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:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = np.arange(0,20,0.5)\n",
    "print(a)\n",
    "print()\n",
    "print(\"Length is: \", len(a))\n",
    "print(\"Step size is: \", a[1]-a[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-5b97dd88408ae9d0",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "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):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = np.arange(0,20.00000001,0.5)\n",
    "print(a)\n",
    "print()\n",
    "print(\"Length is: \", len(a))\n",
    "print(\"Step size is: \", a[1]-a[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-ab1a61494f1f5b33",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "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": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-3a0d9465353f954b",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
Gary Steele's avatar
Gary Steele committed
   "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. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "a = your code\n",
    "print(a)"