{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# SciPy - Library of scientific algorithms for Python"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "J.R. Johansson (jrjohansson at gmail.com)\n",
    "\n",
    "The latest version of this [IPython notebook](http://ipython.org/notebook.html) lecture is available at [http://github.com/jrjohansson/scientific-python-lectures](http://github.com/jrjohansson/scientific-python-lectures).\n",
    "\n",
    "The other notebooks in this lecture series are indexed at [http://jrjohansson.github.io](http://jrjohansson.github.io)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# what is this line all about? Answer in lecture 4\n",
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "from IPython.display import Image"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Introduction"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The SciPy framework builds on top of the low-level NumPy framework for multidimensional arrays, and provides a large number of higher-level scientific algorithms. Some of the topics that SciPy covers are:\n",
    "\n",
    "* Special functions ([scipy.special](http://docs.scipy.org/doc/scipy/reference/special.html))\n",
    "* Integration ([scipy.integrate](http://docs.scipy.org/doc/scipy/reference/integrate.html))\n",
    "* Optimization ([scipy.optimize](http://docs.scipy.org/doc/scipy/reference/optimize.html))\n",
    "* Interpolation ([scipy.interpolate](http://docs.scipy.org/doc/scipy/reference/interpolate.html))\n",
    "* Fourier Transforms ([scipy.fftpack](http://docs.scipy.org/doc/scipy/reference/fftpack.html))\n",
    "* Signal Processing ([scipy.signal](http://docs.scipy.org/doc/scipy/reference/signal.html))\n",
    "* Linear Algebra ([scipy.linalg](http://docs.scipy.org/doc/scipy/reference/linalg.html))\n",
    "* Sparse Eigenvalue Problems ([scipy.sparse](http://docs.scipy.org/doc/scipy/reference/sparse.html))\n",
    "* Statistics ([scipy.stats](http://docs.scipy.org/doc/scipy/reference/stats.html))\n",
    "* Multi-dimensional image processing ([scipy.ndimage](http://docs.scipy.org/doc/scipy/reference/ndimage.html))\n",
    "* File IO ([scipy.io](http://docs.scipy.org/doc/scipy/reference/io.html))\n",
    "\n",
    "Each of these submodules provides a number of functions and classes that can be used to solve problems in their respective topics.\n",
    "\n",
    "In this lecture we will look at how to use some of these subpackages.\n",
    "\n",
    "To access the SciPy package in a Python program, we start by importing everything from the `scipy` module."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from scipy import *"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we only need to use part of the SciPy framework we can selectively include only those modules we are interested in. For example, to include the linear algebra package under the name `la`, we can do:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import scipy.linalg as la"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Special functions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A large number of mathematical special functions are important for many computational physics problems. SciPy provides implementations of a very extensive set of special functions. For details, see the list of functions in the reference documentation at http://docs.scipy.org/doc/scipy/reference/special.html#module-scipy.special. \n",
    "\n",
    "To demonstrate the typical usage of special functions we will look in more detail at the Bessel functions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "#\n",
    "# The scipy.special module includes a large number of Bessel-functions\n",
    "# Here we will use the functions jn and yn, which are the Bessel functions \n",
    "# of the first and second kind and real-valued order. We also include the \n",
    "# function jn_zeros and yn_zeros that gives the zeroes of the functions jn\n",
    "# and yn.\n",
    "#\n",
    "from scipy.special import jn, yn, jn_zeros, yn_zeros"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "J_0(0.000000) = 1.000000\n",
      "Y_0(1.000000) = 0.088257\n"
     ]
    }
   ],
   "source": [
    "n = 0    # order\n",
    "x = 0.0\n",
    "\n",
    "# Bessel function of first kind\n",
    "print \"J_%d(%f) = %f\" % (n, x, jn(n, x))\n",
    "\n",
    "x = 1.0\n",
    "# Bessel function of second kind\n",
    "print \"Y_%d(%f) = %f\" % (n, x, yn(n, x))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 28280 characters]",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x108b03890>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "x = linspace(0, 10, 100)\n",
    "\n",
    "fig, ax = plt.subplots()\n",
    "for n in range(4):\n",
    "    ax.plot(x, jn(n, x), label=r\"$J_%d(x)$\" % n)\n",
    "ax.legend();"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([  2.40482556,   5.52007811,   8.65372791,  11.79153444])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# zeros of Bessel functions\n",
    "n = 0 # order\n",
    "m = 4 # number of roots to compute\n",
    "jn_zeros(n, m)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Integration"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Numerical integration: quadrature"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Numerical evaluation of a function of the type\n",
    "\n",
    "$\\displaystyle \\int_a^b f(x) dx$\n",
    "\n",
    "is called *numerical quadrature*, or simply *quadature*. SciPy provides a series of functions for different kind of quadrature, for example the `quad`, `dblquad` and `tplquad` for single, double and triple integrals, respectively.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from scipy.integrate import quad, dblquad, tplquad"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `quad` function takes a large number of optional arguments, which can be used to fine-tune the behaviour of the function (try `help(quad)` for details).\n",
    "\n",
    "The basic usage is as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# define a simple function for the integrand\n",
    "def f(x):\n",
    "    return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "integral value = 0.5 , absolute error = 5.55111512313e-15\n"
     ]
    }
   ],
   "source": [
    "x_lower = 0 # the lower limit of x\n",
    "x_upper = 1 # the upper limit of x\n",
    "\n",
    "val, abserr = quad(f, x_lower, x_upper)\n",
    "\n",
    "print \"integral value =\", val, \", absolute error =\", abserr "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we need to pass extra arguments to integrand function we can use the `args` keyword argument:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.736675137081 9.3891268825e-13\n"
     ]
    }
   ],
   "source": [
    "def integrand(x, n):\n",
    "    \"\"\"\n",
    "    Bessel function of first kind and order n. \n",
    "    \"\"\"\n",
    "    return jn(n, x)\n",
    "\n",
    "\n",
    "x_lower = 0  # the lower limit of x\n",
    "x_upper = 10 # the upper limit of x\n",
    "\n",
    "val, abserr = quad(integrand, x_lower, x_upper, args=(3,))\n",
    "\n",
    "print val, abserr "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For simple functions we can use a lambda function (name-less function) instead of explicitly defining a function for the integrand:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "numerical  = 1.77245385091 1.42026367809e-08\n",
      "analytical = 1.77245385091\n"
     ]
    }
   ],
   "source": [
    "val, abserr = quad(lambda x: exp(-x ** 2), -Inf, Inf)\n",
    "\n",
    "print \"numerical  =\", val, abserr\n",
    "\n",
    "analytical = sqrt(pi)\n",
    "print \"analytical =\", analytical"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As show in the example above, we can also use 'Inf' or '-Inf' as integral limits.\n",
    "\n",
    "Higher-dimensional integration works in the same way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.785398163397 1.63822994214e-13\n"
     ]
    }
   ],
   "source": [
    "def integrand(x, y):\n",
    "    return exp(-x**2-y**2)\n",
    "\n",
    "x_lower = 0  \n",
    "x_upper = 10\n",
    "y_lower = 0\n",
    "y_upper = 10\n",
    "\n",
    "val, abserr = dblquad(integrand, x_lower, x_upper, lambda x : y_lower, lambda x: y_upper)\n",
    "\n",
    "print val, abserr "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note how we had to pass lambda functions for the limits for the y integration, since these in general can be functions of x."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ordinary differential equations (ODEs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "SciPy provides two different ways to solve ODEs: An API based on the function `odeint`, and object-oriented API based on the class `ode`. Usually `odeint` is easier to get started with, but the `ode` class offers some finer level of control.\n",
    "\n",
    "Here we will use the `odeint` functions. For more information about the class `ode`, try `help(ode)`. It does pretty much the same thing as `odeint`, but in an object-oriented fashion.\n",
    "\n",
    "To use `odeint`, first import it from the `scipy.integrate` module"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from scipy.integrate import odeint, ode"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A system of ODEs are usually formulated on standard form before it is attacked numerically. The standard form is:\n",
    "\n",
    "$y' = f(y, t)$\n",
    "\n",
    "where \n",
    "\n",
    "$y = [y_1(t), y_2(t), ..., y_n(t)]$ \n",
    "\n",
    "and $f$ is some function that gives the derivatives of the function $y_i(t)$. To solve an ODE we need to know the function $f$ and an initial condition, $y(0)$.\n",
    "\n",
    "Note that higher-order ODEs can always be written in this form by introducing new variables for the intermediate derivatives.\n",
    "\n",
    "Once we have defined the Python function `f` and array `y_0` (that is $f$ and $y(0)$ in the mathematical formulation), we can use the `odeint` function as:\n",
    "\n",
    "    y_t = odeint(f, y_0, t)\n",
    "\n",
    "where `t` is and array with time-coordinates for which to solve the ODE problem. `y_t` is an array with one row for each point in time in `t`, where each column corresponds to a solution `y_i(t)` at that point in time. \n",
    "\n",
    "We will see how we can implement `f` and `y_0` in Python code in the examples below."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Example: double pendulum"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's consider a physical example: The double compound pendulum, described in some detail here: http://en.wikipedia.org/wiki/Double_pendulum"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<img src=\"http://upload.wikimedia.org/wikipedia/commons/c/c9/Double-compound-pendulum-dimensioned.svg\"/>"
      ],
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Image(url='http://upload.wikimedia.org/wikipedia/commons/c/c9/Double-compound-pendulum-dimensioned.svg')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The equations of motion of the pendulum are given on the wiki page:\n",
    "\n",
    "${\\dot \\theta_1} = \\frac{6}{m\\ell^2} \\frac{ 2 p_{\\theta_1} - 3 \\cos(\\theta_1-\\theta_2) p_{\\theta_2}}{16 - 9 \\cos^2(\\theta_1-\\theta_2)}$\n",
    "\n",
    "${\\dot \\theta_2} = \\frac{6}{m\\ell^2} \\frac{ 8 p_{\\theta_2} - 3 \\cos(\\theta_1-\\theta_2) p_{\\theta_1}}{16 - 9 \\cos^2(\\theta_1-\\theta_2)}.$\n",
    "\n",
    "${\\dot p_{\\theta_1}} = -\\frac{1}{2} m \\ell^2 \\left [ {\\dot \\theta_1} {\\dot \\theta_2} \\sin (\\theta_1-\\theta_2) + 3 \\frac{g}{\\ell} \\sin \\theta_1 \\right ]$\n",
    "\n",
    "${\\dot p_{\\theta_2}} = -\\frac{1}{2} m \\ell^2 \\left [ -{\\dot \\theta_1} {\\dot \\theta_2} \\sin (\\theta_1-\\theta_2) +  \\frac{g}{\\ell} \\sin \\theta_2 \\right]$\n",
    "\n",
    "To make the Python code simpler to follow, let's introduce new variable names and the vector notation: $x = [\\theta_1, \\theta_2, p_{\\theta_1}, p_{\\theta_2}]$\n",
    "\n",
    "${\\dot x_1} = \\frac{6}{m\\ell^2} \\frac{ 2 x_3 - 3 \\cos(x_1-x_2) x_4}{16 - 9 \\cos^2(x_1-x_2)}$\n",
    "\n",
    "${\\dot x_2} = \\frac{6}{m\\ell^2} \\frac{ 8 x_4 - 3 \\cos(x_1-x_2) x_3}{16 - 9 \\cos^2(x_1-x_2)}$\n",
    "\n",
    "${\\dot x_3} = -\\frac{1}{2} m \\ell^2 \\left [ {\\dot x_1} {\\dot x_2} \\sin (x_1-x_2) + 3 \\frac{g}{\\ell} \\sin x_1 \\right ]$\n",
    "\n",
    "${\\dot x_4} = -\\frac{1}{2} m \\ell^2 \\left [ -{\\dot x_1} {\\dot x_2} \\sin (x_1-x_2) +  \\frac{g}{\\ell} \\sin x_2 \\right]$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "g = 9.82\n",
    "L = 0.5\n",
    "m = 0.1\n",
    "\n",
    "def dx(x, t):\n",
    "    \"\"\"\n",
    "    The right-hand side of the pendulum ODE\n",
    "    \"\"\"\n",
    "    x1, x2, x3, x4 = x[0], x[1], x[2], x[3]\n",
    "    \n",
    "    dx1 = 6.0/(m*L**2) * (2 * x3 - 3 * cos(x1-x2) * x4)/(16 - 9 * cos(x1-x2)**2)\n",
    "    dx2 = 6.0/(m*L**2) * (8 * x4 - 3 * cos(x1-x2) * x3)/(16 - 9 * cos(x1-x2)**2)\n",
    "    dx3 = -0.5 * m * L**2 * ( dx1 * dx2 * sin(x1-x2) + 3 * (g/L) * sin(x1))\n",
    "    dx4 = -0.5 * m * L**2 * (-dx1 * dx2 * sin(x1-x2) + (g/L) * sin(x2))\n",
    "    \n",
    "    return [dx1, dx2, dx3, dx4]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# choose an initial state\n",
    "x0 = [pi/4, pi/2, 0, 0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# time coordinate to solve the ODE for: from 0 to 10 seconds\n",
    "t = linspace(0, 10, 250)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# solve the ODE problem\n",
    "x = odeint(dx, x0, t)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 56652 characters]",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x108f90a50>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# plot the angles as a function of time\n",
    "\n",
    "fig, axes = plt.subplots(1,2, figsize=(12,4))\n",
    "axes[0].plot(t, x[:, 0], 'r', label=\"theta1\")\n",
    "axes[0].plot(t, x[:, 1], 'b', label=\"theta2\")\n",
    "\n",
    "\n",
    "x1 = + L * sin(x[:, 0])\n",
    "y1 = - L * cos(x[:, 0])\n",
    "\n",
    "x2 = x1 + L * sin(x[:, 1])\n",
    "y2 = y1 - L * cos(x[:, 1])\n",
    "    \n",
    "axes[1].plot(x1, y1, 'r', label=\"pendulum1\")\n",
    "axes[1].plot(x2, y2, 'b', label=\"pendulum2\")\n",
    "axes[1].set_ylim([-1, 0])\n",
    "axes[1].set_xlim([1, -1]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Simple animation of the pendulum motion. We will see how to make better animation in Lecture 4."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from IPython.display import display, clear_output\n",
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 5304 characters]",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x108af5f90>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 5304 characters]",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x108af5f90>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax = plt.subplots(figsize=(4,4))\n",
    "\n",
    "for t_idx, tt in enumerate(t[:200]):\n",
    "\n",
    "    x1 = + L * sin(x[t_idx, 0])\n",
    "    y1 = - L * cos(x[t_idx, 0])\n",
    "\n",
    "    x2 = x1 + L * sin(x[t_idx, 1])\n",
    "    y2 = y1 - L * cos(x[t_idx, 1])\n",
    "    \n",
    "    ax.cla()    \n",
    "    ax.plot([0, x1], [0, y1], 'r.-')\n",
    "    ax.plot([x1, x2], [y1, y2], 'b.-')\n",
    "    ax.set_ylim([-1.5, 0.5])\n",
    "    ax.set_xlim([1, -1])\n",
    "\n",
    "    clear_output() \n",
    "    display(fig)\n",
    "\n",
    "    time.sleep(0.1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Example: Damped harmonic oscillator"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "ODE problems are important in computational physics, so we will look at one more example: the damped harmonic oscillation. This problem is well described on the wiki page: http://en.wikipedia.org/wiki/Damping\n",
    "\n",
    "The equation of motion for the damped oscillator is:\n",
    "\n",
    "$\\displaystyle \\frac{\\mathrm{d}^2x}{\\mathrm{d}t^2} + 2\\zeta\\omega_0\\frac{\\mathrm{d}x}{\\mathrm{d}t} + \\omega^2_0 x = 0$\n",
    "\n",
    "where $x$ is the position of the oscillator, $\\omega_0$ is the frequency, and $\\zeta$ is the damping ratio. To write this second-order ODE on standard form we introduce $p = \\frac{\\mathrm{d}x}{\\mathrm{d}t}$:\n",
    "\n",
    "$\\displaystyle \\frac{\\mathrm{d}p}{\\mathrm{d}t} = - 2\\zeta\\omega_0 p - \\omega^2_0 x$\n",
    "\n",
    "$\\displaystyle \\frac{\\mathrm{d}x}{\\mathrm{d}t} = p$\n",
    "\n",
    "In the implementation of this example we will add extra arguments to the RHS function for the ODE, rather than using global variables as we did in the previous example. As a consequence of the extra arguments to the RHS, we need to pass an keyword argument `args` to the `odeint` function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def dy(y, t, zeta, w0):\n",
    "    \"\"\"\n",
    "    The right-hand side of the damped oscillator ODE\n",
    "    \"\"\"\n",
    "    x, p = y[0], y[1]\n",
    "    \n",
    "    dx = p\n",
    "    dp = -2 * zeta * w0 * p - w0**2 * x\n",
    "\n",
    "    return [dx, dp]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# initial state: \n",
    "y0 = [1.0, 0.0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# time coordinate to solve the ODE for\n",
    "t = linspace(0, 10, 1000)\n",
    "w0 = 2*pi*1.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# solve the ODE problem for three different values of the damping ratio\n",
    "\n",
    "y1 = odeint(dy, y0, t, args=(0.0, w0)) # undamped\n",
    "y2 = odeint(dy, y0, t, args=(0.2, w0)) # under damped\n",
    "y3 = odeint(dy, y0, t, args=(1.0, w0)) # critical damping\n",
    "y4 = odeint(dy, y0, t, args=(5.0, w0)) # over damped"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 30496 characters]",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x1093bf4d0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax = plt.subplots()\n",
    "ax.plot(t, y1[:,0], 'k', label=\"undamped\", linewidth=0.25)\n",
    "ax.plot(t, y2[:,0], 'r', label=\"under damped\")\n",
    "ax.plot(t, y3[:,0], 'b', label=r\"critical damping\")\n",
    "ax.plot(t, y4[:,0], 'g', label=\"over damped\")\n",
    "ax.legend();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Fourier transform"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fourier transforms are one of the universal tools in computational physics, which appear over and over again in different contexts. SciPy provides functions for accessing the classic [FFTPACK](http://www.netlib.org/fftpack/) library from NetLib, which is an efficient and well tested FFT library written in FORTRAN. The SciPy API has a few additional convenience functions, but overall the API is closely related to the original FORTRAN library.\n",
    "\n",
    "To use the `fftpack` module in a python program, include it using:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from numpy.fft import fftfreq\n",
    "from scipy.fftpack import *"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To demonstrate how to do a fast Fourier transform with SciPy, let's look at the FFT of the solution to the damped oscillator from the previous section:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "N = len(t)\n",
    "dt = t[1]-t[0]\n",
    "\n",
    "# calculate the fast fourier transform\n",
    "# y2 is the solution to the under-damped oscillator from the previous section\n",
    "F = fft(y2[:,0]) \n",
    "\n",
    "# calculate the frequencies for the components in F\n",
    "w = fftfreq(N, dt)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 9988 characters]",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x108e03b10>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax = plt.subplots(figsize=(9,3))\n",
    "ax.plot(w, abs(F));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since the signal is real, the spectrum is symmetric. We therefore only need to plot the part that corresponds to the positive frequencies. To extract that part of the `w` and `F` we can use some of the indexing tricks for NumPy arrays that we saw in Lecture 2:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "indices = where(w > 0) # select only indices for elements that corresponds to positive frequencies\n",
    "w_pos = w[indices]\n",
    "F_pos = F[indices]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 10956 characters]",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x108d77690>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax = plt.subplots(figsize=(9,3))\n",
    "ax.plot(w_pos, abs(F_pos))\n",
    "ax.set_xlim(0, 5);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As expected, we now see a peak in the spectrum that is centered around 1, which is the frequency we used in the damped oscillator example."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Linear algebra"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The linear algebra module contains a lot of matrix related functions, including linear equation solving, eigenvalue solvers, matrix functions (for example matrix-exponentiation), a number of different decompositions (SVD, LU, cholesky), etc. \n",
    "\n",
    "Detailed documentation is available at: http://docs.scipy.org/doc/scipy/reference/linalg.html\n",
    "\n",
    "Here we will look at how to use some of these functions:\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Linear equation systems"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Linear equation systems on the matrix form\n",
    "\n",
    "$A x = b$\n",
    "\n",
    "where $A$ is a matrix and $x,b$ are vectors can be solved like:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from scipy.linalg import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "A = array([[1,2,3], [4,5,6], [7,8,9]])\n",
    "b = array([1,2,3])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([-0.33333333,  0.66666667,  0.        ])"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = solve(A, b)\n",
    "\n",
    "x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ -1.11022302e-16,   0.00000000e+00,   0.00000000e+00])"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# check\n",
    "dot(A, x) - b"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also do the same with\n",
    "\n",
    "$A X = B$\n",
    "\n",
    "where $A, B, X$ are matrices:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "A = rand(3,3)\n",
    "B = rand(3,3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "X = solve(A, B)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 1.19168749,  1.34543171,  0.38437594],\n",
       "       [-0.88153715, -3.22735597,  0.66370273],\n",
       "       [ 0.10044006,  1.0465058 ,  0.39801748]])"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2.0014830212433605e-16"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# check\n",
    "norm(dot(A, X) - B)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Eigenvalues and eigenvectors"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The eigenvalue problem for a matrix $A$:\n",
    "\n",
    "$\\displaystyle A v_n = \\lambda_n v_n$\n",
    "\n",
    "where $v_n$ is the $n$th eigenvector and $\\lambda_n$ is the $n$th eigenvalue.\n",
    "\n",
    "To calculate eigenvalues of a matrix, use the `eigvals` and for calculating both eigenvalues and eigenvectors, use the function `eig`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "evals = eigvals(A)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 1.08466629+0.j,  0.33612878+0.j, -0.28229973+0.j])"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "evals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "evals, evecs = eig(A)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 1.08466629+0.j,  0.33612878+0.j, -0.28229973+0.j])"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "evals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[-0.20946865, -0.48428024, -0.14392087],\n",
       "       [-0.79978578,  0.8616452 , -0.79527482],\n",
       "       [-0.56255275,  0.15178997,  0.58891829]])"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "evecs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The eigenvectors corresponding to the $n$th eigenvalue (stored in `evals[n]`) is the $n$th *column* in `evecs`, i.e., `evecs[:,n]`. To verify this, let's try multiplying eigenvectors with the matrix and compare to the product of the eigenvector and the eigenvalue:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3.243515426387745e-16"
      ]
     },
     "execution_count": 46,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "n = 1\n",
    "\n",
    "norm(dot(A, evecs[:,n]) - evals[n] * evecs[:,n])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are also more specialized eigensolvers, like the `eigh` for Hermitian matrices. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Matrix operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 2.0031935 , -0.63411453,  0.49891784],\n",
       "       [-4.63643938, -0.2212669 ,  3.35170585],\n",
       "       [ 1.06421936,  1.37366073, -1.42726809]])"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# the matrix inverse\n",
    "inv(A)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-0.10292296739753022"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# determinant\n",
    "det(A)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1.3060382297688262, 1.591998214728641)"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# norms of various orders\n",
    "norm(A, ord=2), norm(A, ord=Inf)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Sparse matrices"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sparse matrices are often useful in numerical simulations dealing with large systems, if the problem can be described in matrix form where the matrices or vectors mostly contains zeros. Scipy has a good support for sparse matrices, with basic linear algebra operations (such as equation solving, eigenvalue calculations, etc.).\n",
    "\n",
    "There are many possible strategies for storing sparse matrices in an efficient way. Some of the most common are the so-called coordinate form (COO), list of list (LIL) form,  and compressed-sparse column CSC (and row, CSR). Each format has some advantages and disadvantages. Most computational algorithms (equation solving, matrix-matrix multiplication, etc.) can be efficiently implemented using CSR or CSC formats, but they are not so intuitive and not so easy to initialize. So often a sparse matrix is initially created in COO or LIL format (where we can efficiently add elements to the sparse matrix data), and then converted to CSC or CSR before used in real calculations.\n",
    "\n",
    "For more information about these sparse formats, see e.g. http://en.wikipedia.org/wiki/Sparse_matrix\n",
    "\n",
    "When we create a sparse matrix we have to choose which format it should be stored in. For example, "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from scipy.sparse import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1, 0, 0, 0],\n",
       "       [0, 3, 0, 0],\n",
       "       [0, 1, 1, 0],\n",
       "       [1, 0, 0, 1]])"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# dense matrix\n",
    "M = array([[1,0,0,0], [0,3,0,0], [0,1,1,0], [1,0,0,1]]); M"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<4x4 sparse matrix of type '<type 'numpy.int64'>'\n",
       "\twith 6 stored elements in Compressed Sparse Row format>"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# convert from dense to sparse\n",
    "A = csr_matrix(M); A"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "matrix([[1, 0, 0, 0],\n",
       "        [0, 3, 0, 0],\n",
       "        [0, 1, 1, 0],\n",
       "        [1, 0, 0, 1]])"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# convert from sparse to dense\n",
    "A.todense()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "More efficient way to create sparse matrices: create an empty matrix and populate with using matrix indexing (avoids creating a potentially large dense matrix)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<4x4 sparse matrix of type '<type 'numpy.float64'>'\n",
       "\twith 6 stored elements in LInked List format>"
      ]
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "A = lil_matrix((4,4)) # empty 4x4 sparse matrix\n",
    "A[0,0] = 1\n",
    "A[1,1] = 3\n",
    "A[2,2] = A[2,1] = 1\n",
    "A[3,3] = A[3,0] = 1\n",
    "A"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "matrix([[ 1.,  0.,  0.,  0.],\n",
       "        [ 0.,  3.,  0.,  0.],\n",
       "        [ 0.,  1.,  1.,  0.],\n",
       "        [ 1.,  0.,  0.,  1.]])"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "A.todense()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Converting between different sparse matrix formats:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<4x4 sparse matrix of type '<type 'numpy.float64'>'\n",
       "\twith 6 stored elements in LInked List format>"
      ]
     },
     "execution_count": 56,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "A"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<4x4 sparse matrix of type '<type 'numpy.float64'>'\n",
       "\twith 6 stored elements in Compressed Sparse Row format>"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "A = csr_matrix(A); A"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<4x4 sparse matrix of type '<type 'numpy.float64'>'\n",
       "\twith 6 stored elements in Compressed Sparse Column format>"
      ]
     },
     "execution_count": 58,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "A = csc_matrix(A); A"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can compute with sparse matrices like with dense matrices:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "matrix([[ 1.,  0.,  0.,  0.],\n",
       "        [ 0.,  3.,  0.,  0.],\n",
       "        [ 0.,  1.,  1.,  0.],\n",
       "        [ 1.,  0.,  0.,  1.]])"
      ]
     },
     "execution_count": 59,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "A.todense()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "matrix([[ 1.,  0.,  0.,  0.],\n",
       "        [ 0.,  9.,  0.,  0.],\n",
       "        [ 0.,  4.,  1.,  0.],\n",
       "        [ 2.,  0.,  0.,  1.]])"
      ]
     },
     "execution_count": 60,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(A * A).todense()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "matrix([[ 1.,  0.,  0.,  0.],\n",
       "        [ 0.,  3.,  0.,  0.],\n",
       "        [ 0.,  1.,  1.,  0.],\n",
       "        [ 1.,  0.,  0.,  1.]])"
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "A.todense()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "matrix([[ 1.,  0.,  0.,  0.],\n",
       "        [ 0.,  9.,  0.,  0.],\n",
       "        [ 0.,  4.,  1.,  0.],\n",
       "        [ 2.,  0.,  0.,  1.]])"
      ]
     },
     "execution_count": 62,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "A.dot(A).todense()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1],\n",
       "       [2],\n",
       "       [3],\n",
       "       [4]])"
      ]
     },
     "execution_count": 63,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "v = array([1,2,3,4])[:,newaxis]; v"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 1.],\n",
       "       [ 6.],\n",
       "       [ 5.],\n",
       "       [ 5.]])"
      ]
     },
     "execution_count": 64,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# sparse matrix - dense vector multiplication\n",
    "A * v"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "matrix([[ 1.],\n",
       "        [ 6.],\n",
       "        [ 5.],\n",
       "        [ 5.]])"
      ]
     },
     "execution_count": 65,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# same result with dense matrix - dense vector multiplication\n",
    "A.todense() * v"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Optimization"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Optimization (finding minima or maxima of a function) is a large field in mathematics, and optimization of complicated functions or in many variables can be rather involved. Here we will only look at a few very simple cases. For a more detailed introduction to optimization with SciPy see: http://scipy-lectures.github.com/advanced/mathematical_optimization/index.html\n",
    "\n",
    "To use the optimization module in scipy first include the `optimize` module:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from scipy import optimize"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Finding a minima"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's first look at how to find the minima of a simple function of a single variable:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def f(x):\n",
    "    return 4*x**3 + (x-2)**2 + x**4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 10740 characters]",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x109feb690>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax  = plt.subplots()\n",
    "x = linspace(-5, 3, 100)\n",
    "ax.plot(x, f(x));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can use the `fmin_bfgs` function to find the minima of a function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimization terminated successfully.\n",
      "         Current function value: -3.506641\n",
      "         Iterations: 6\n",
      "         Function evaluations: 30\n",
      "         Gradient evaluations: 10\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([-2.67298164])"
      ]
     },
     "execution_count": 69,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x_min = optimize.fmin_bfgs(f, -2)\n",
    "x_min "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimization terminated successfully.\n",
      "         Current function value: 2.804988\n",
      "         Iterations: 3\n",
      "         Function evaluations: 15\n",
      "         Gradient evaluations: 5\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([ 0.46961745])"
      ]
     },
     "execution_count": 70,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "optimize.fmin_bfgs(f, 0.5) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also use the `brent` or `fminbound` functions. They have a bit different syntax and use different algorithms. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.46961743402759754"
      ]
     },
     "execution_count": 71,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "optimize.brent(f)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-2.6729822917513886"
      ]
     },
     "execution_count": 72,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "optimize.fminbound(f, -4, 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Finding a solution to a function"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To find the root for a function of the form $f(x) = 0$ we can use the `fsolve` function. It requires an initial guess: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "omega_c = 3.0\n",
    "def f(omega):\n",
    "    # a transcendental equation: resonance frequencies of a low-Q SQUID terminated microwave resonator\n",
    "    return tan(2*pi*omega) - omega_c/omega"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/Users/rob/miniconda/envs/py27-spl/lib/python2.7/site-packages/IPython/kernel/__main__.py:4: RuntimeWarning: divide by zero encountered in divide\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 17720 characters]",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x10a14e610>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax  = plt.subplots(figsize=(10,4))\n",
    "x = linspace(0, 3, 1000)\n",
    "y = f(x)\n",
    "mask = where(abs(y) > 50)\n",
    "x[mask] = y[mask] = NaN # get rid of vertical line when the function flip sign\n",
    "ax.plot(x, y)\n",
    "ax.plot([0, 3], [0, 0], 'k')\n",
    "ax.set_ylim(-5,5);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0.23743014])"
      ]
     },
     "execution_count": 75,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "optimize.fsolve(f, 0.1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0.71286972])"
      ]
     },
     "execution_count": 76,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "optimize.fsolve(f, 0.6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 1.18990285])"
      ]
     },
     "execution_count": 77,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "optimize.fsolve(f, 1.1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Interpolation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Interpolation is simple and convenient in scipy: The `interp1d` function, when given arrays describing X and Y data, returns and object that behaves like a function that can be called for an arbitrary value of x (in the range covered by X), and it returns the corresponding interpolated y value:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from scipy.interpolate import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def f(x):\n",
    "    return sin(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "n = arange(0, 10)  \n",
    "x = linspace(0, 9, 100)\n",
    "\n",
    "y_meas = f(n) + 0.1 * randn(len(n)) # simulate measurement with noise\n",
    "y_real = f(x)\n",
    "\n",
    "linear_interpolation = interp1d(n, y_meas)\n",
    "y_interp1 = linear_interpolation(x)\n",
    "\n",
    "cubic_interpolation = interp1d(n, y_meas, kind='cubic')\n",
    "y_interp2 = cubic_interpolation(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 42456 characters]",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x10a5c8cd0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax = plt.subplots(figsize=(10,4))\n",
    "ax.plot(n, y_meas, 'bs', label='noisy data')\n",
    "ax.plot(x, y_real, 'k', lw=2, label='true function')\n",
    "ax.plot(x, y_interp1, 'r', label='linear interp')\n",
    "ax.plot(x, y_interp2, 'g', label='cubic interp')\n",
    "ax.legend(loc=3);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Statistics"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `scipy.stats` module contains a large number of statistical distributions, statistical functions and tests. For a complete documentation of its features, see http://docs.scipy.org/doc/scipy/reference/stats.html.\n",
    "\n",
    "There is also a very powerful python package for statistical modelling called statsmodels. See http://statsmodels.sourceforge.net for more details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from scipy import stats"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# create a (discrete) random variable with Poissonian distribution\n",
    "\n",
    "X = stats.poisson(3.5) # photon distribution for a coherent state with n=3.5 photons"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 8332 characters]",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x10aa71fd0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "n = arange(0,15)\n",
    "\n",
    "fig, axes = plt.subplots(3,1, sharex=True)\n",
    "\n",
    "# plot the probability mass function (PMF)\n",
    "axes[0].step(n, X.pmf(n))\n",
    "\n",
    "# plot the cumulative distribution function (CDF)\n",
    "axes[1].step(n, X.cdf(n))\n",
    "\n",
    "# plot histogram of 1000 random realizations of the stochastic variable X\n",
    "axes[2].hist(X.rvs(size=1000));"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# create a (continuous) random variable with normal distribution\n",
    "Y = stats.norm()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 13364 characters]",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x10ad17150>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "x = linspace(-5,5,100)\n",
    "\n",
    "fig, axes = plt.subplots(3,1, sharex=True)\n",
    "\n",
    "# plot the probability distribution function (PDF)\n",
    "axes[0].plot(x, Y.pdf(x))\n",
    "\n",
    "# plot the cumulative distribution function (CDF)\n",
    "axes[1].plot(x, Y.cdf(x));\n",
    "\n",
    "# plot histogram of 1000 random realizations of the stochastic variable Y\n",
    "axes[2].hist(Y.rvs(size=1000), bins=50);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Statistics:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(3.5, 1.8708286933869707, 3.5)"
      ]
     },
     "execution_count": 87,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X.mean(), X.std(), X.var() # Poisson distribution"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(0.0, 1.0, 1.0)"
      ]
     },
     "execution_count": 88,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Y.mean(), Y.std(), Y.var() # normal distribution"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Statistical tests"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Test if two sets of (independent) random data come from the same distribution:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "t-statistic = -0.901953297251\n",
      "p-value = 0.367190391714\n"
     ]
    }
   ],
   "source": [
    "t_statistic, p_value = stats.ttest_ind(X.rvs(size=1000), X.rvs(size=1000))\n",
    "\n",
    "print \"t-statistic =\", t_statistic\n",
    "print \"p-value =\", p_value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since the p value is very large we cannot reject the hypothesis that the two sets of random data have *different* means."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To test if the mean of a single sample of data has mean 0.1 (the true mean is 0.0):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Ttest_1sampResult(statistic=-3.1644288210071765, pvalue=0.0016008455559249511)"
      ]
     },
     "execution_count": 90,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "stats.ttest_1samp(Y.rvs(size=1000), 0.1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Low p-value means that we can reject the hypothesis that the mean of Y is 0.1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.0"
      ]
     },
     "execution_count": 91,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Y.mean()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Ttest_1sampResult(statistic=2.2098772438652992, pvalue=0.027339807364469011)"
      ]
     },
     "execution_count": 92,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "stats.ttest_1samp(Y.rvs(size=1000), Y.mean())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Further reading"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* http://www.scipy.org - The official web page for the SciPy project.\n",
    "* http://docs.scipy.org/doc/scipy/reference/tutorial/index.html - A tutorial on how to get started using SciPy. \n",
    "* https://github.com/scipy/scipy/ - The SciPy source code. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Versions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 93,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "application/json": {
       "Software versions": [
        {
         "module": "Python",
         "version": "2.7.10 64bit [GCC 4.2.1 (Apple Inc. build 5577)]"
        },
        {
         "module": "IPython",
         "version": "3.2.1"
        },
        {
         "module": "OS",
         "version": "Darwin 14.1.0 x86_64 i386 64bit"
        },
        {
         "module": "numpy",
         "version": "1.9.2"
        },
        {
         "module": "matplotlib",
         "version": "1.4.3"
        },
        {
         "module": "scipy",
         "version": "0.16.0"
        }
       ]
      },
      "text/html": [
       "<table><tr><th>Software</th><th>Version</th></tr><tr><td>Python</td><td>2.7.10 64bit [GCC 4.2.1 (Apple Inc. build 5577)]</td></tr><tr><td>IPython</td><td>3.2.1</td></tr><tr><td>OS</td><td>Darwin 14.1.0 x86_64 i386 64bit</td></tr><tr><td>numpy</td><td>1.9.2</td></tr><tr><td>matplotlib</td><td>1.4.3</td></tr><tr><td>scipy</td><td>0.16.0</td></tr><tr><td colspan='2'>Sat Aug 15 11:13:18 2015 JST</td></tr></table>"
      ],
      "text/latex": [
       "\\begin{tabular}{|l|l|}\\hline\n",
       "{\\bf Software} & {\\bf Version} \\\\ \\hline\\hline\n",
       "Python & 2.7.10 64bit [GCC 4.2.1 (Apple Inc. build 5577)] \\\\ \\hline\n",
       "IPython & 3.2.1 \\\\ \\hline\n",
       "OS & Darwin 14.1.0 x86\\_64 i386 64bit \\\\ \\hline\n",
       "numpy & 1.9.2 \\\\ \\hline\n",
       "matplotlib & 1.4.3 \\\\ \\hline\n",
       "scipy & 0.16.0 \\\\ \\hline\n",
       "\\hline \\multicolumn{2}{|l|}{Sat Aug 15 11:13:18 2015 JST} \\\\ \\hline\n",
       "\\end{tabular}\n"
      ],
      "text/plain": [
       "Software versions\n",
       "Python 2.7.10 64bit [GCC 4.2.1 (Apple Inc. build 5577)]\n",
       "IPython 3.2.1\n",
       "OS Darwin 14.1.0 x86_64 i386 64bit\n",
       "numpy 1.9.2\n",
       "matplotlib 1.4.3\n",
       "scipy 0.16.0\n",
       "Sat Aug 15 11:13:18 2015 JST"
      ]
     },
     "execution_count": 93,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%reload_ext version_information\n",
    "\n",
    "%version_information numpy, matplotlib, scipy"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
