{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[Table of Contents](table_of_contents.ipynb)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Topic 10.  Generalized Fourier Series\n",
    "Author: Shea Smith\n",
    "Email: SerShea.Smith@gmail.com\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  Introduction\n",
    "High level explanation:\n",
    "\n",
    "The Fourier series was initiaally created to provide a solution to the heat equation. Joseph Fourier found that he could solve this by superimposing sinusoids on top of one another. This superposition is know as the Fourier Series.\n",
    "\n",
    "In math, the Fourier series is a peridodic function made up of sinusoids. These sinusoids can also be thought of as rotating vectors in the imaginary plane. When these sinusoids, or vectors are summed and weighted, they can be used to represent other signals and provide solutions to otherwise difficult functions. \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Explanation of the theory\n",
    "  \n",
    "The emphasis here is clarity for future students learning the topic:\n",
    "\n",
    "We are interested often in approximating continuous functions on L_2. \n",
    "\n",
    "The idea behind the generalized Fourier series comes from the definition of a complete basis.\n",
    "Definition (Complete Basis)\n",
    "An orthonormal set {pi, i = 1, . . . , ∞} in a Hilbert space S is a\n",
    "complete basis or total basis if ∀x ∈ S\n",
    "\n",
    "Note that if: \n",
    "\\begin{equation}\n",
    "x = \\sum_{i=1}^{\\infty} <c_i,p_i>  and  <p_i,p_j> = \\delta_{ij}\n",
    "\\end{equation}\n",
    "Then:\n",
    "\\begin{equation}\n",
    "<x,p_j> = \\sum_{j=1}^{\\infty} c_i<p_i,p_j> = c_j\n",
    "\\end{equation}\n",
    "\n",
    "From the definition above, we can \n",
    "The generalized Fourier Series can be written as:\n",
    "\\begin{equation}\n",
    "x = \\sum_{i=1}^{\\infty} <x,p_i>p_i\n",
    "\\end{equation}\n",
    "\n",
    "As a familiar example, we often see continuous time signal or functions in L_2[0,T] written as a summation:\n",
    "\\begin{equation}\n",
    "f(t) = \\sum_{n=-\\infty}^{\\infty} c_n\\frac{1}{T}e^{j(\\frac{2\\pi}{T})nt}\n",
    "\\end{equation}\n",
    "With this summation, any arbritary function can be expressed or approximated. In this summation c_n, or the constants that lead the fourier series' terms are given by:\n",
    "\\begin{equation}\n",
    "c_n = <f,\\frac{1}{T}e^{-j(\\frac{2\\pi}{T})nt}> = \\frac{1}{T}\\int_0^Tf(t)e^{j(\\frac{2\\pi}{T})nt}dt\n",
    "\\end{equation}\n",
    "\n",
    "From this final equation we can solve for all constants in the generalized fourier series. With these constants the function f(t) can be accuratly represented with the scaled fourier series."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Simple Numerical Examples\n",
    "\n",
    "This is a simple visual to how an incresing sum of sinusoids approximates a step response. In this case, the summation takes the form of:\n",
    "\n",
    "\\begin{equation}\n",
    "y(t) = \\frac{-4}{\\pi}(\\frac{cos(1\\pi t)}{1} - \\frac{cos(3\\pi t)}{3} + \\frac{cos(5\\pi t)}{5} -...)\n",
    "\\end{equation}\n",
    "\n",
    "As this summation approches infinity, it models the step response.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Here we see an example of a fourier series approximating an impulse function.\n",
      "As the number of summed sinusoids in the series increases the approximation becomes better. \n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "0a69d611d15f4a12b63962ec5a4cd708",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "interactive(children=(IntSlider(value=1, description='n', max=128, min=1, step=2), Output()), _dom_classes=('w…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "<function __main__.f(n)>"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from __future__ import print_function\n",
    "from ipywidgets import interact, interactive, fixed, interact_manual\n",
    "import ipywidgets as widgets\n",
    "\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from numpy import pi\n",
    "\n",
    "prec = 1000\n",
    "\n",
    "x = np.arange(0,1,1/prec)\n",
    "y = np.ones(prec)\n",
    "y[:int(prec/2)] = -1\n",
    "\n",
    "def f(n):\n",
    "    xf = np.arange(0,1,1/prec)\n",
    "    yf = np.zeros(prec)\n",
    "    kk=0 \n",
    "    Y=0\n",
    "    for v in x:\n",
    "        fp = -4/pi\n",
    "        for k in range(1,n+1,2):\n",
    "            yf[kk] = yf[kk] + fp*np.cos(k*pi*v) / k\n",
    "            fp = fp*-1\n",
    "        kk = kk +1\n",
    "    plt.plot(x,y,'r.',label='impulse')\n",
    "    plt.plot(xf,yf,label='fourier approx.')\n",
    "    plt.legend()\n",
    "    \n",
    "print(\"Here we see an example of a fourier series approximating an impulse function.\")\n",
    "print(\"As the number of summed sinusoids in the series increases the approximation becomes better. \")\n",
    "\n",
    "interact(f, n=widgets.IntSlider(min=1, max=128, step=2, value=1))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## An Engineering Application\n",
    "\n",
    "The example above was a simpler model that was a summation of cosines, but what about a solution that works for all functions? This example employs the generalized form of the Fourier series that can be used to approximate continuous function in L_2. Try running the code below and see how many iterations it would take to approximate sinusoids to the example function:\n",
    "\n",
    "\\begin{equation}\n",
    "g(t) = 20e^{-t/2}\n",
    "\\end{equation}\n",
    "\n",
    "The generalized fourier series can even track noisy functions, try adjusting the noise level below as well for an added depth to the simulation. You can even change the function listed below to approximate your own function.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "1a17f0cdaaac4b01b9d5c145b3aaf4e2",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "interactive(children=(IntSlider(value=2, description='n', max=1000, min=2, step=10), FloatSlider(value=0.0, de…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "<function __main__.f(n, noise_level)>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from __future__ import print_function\n",
    "from ipywidgets import interact, interactive, fixed, interact_manual\n",
    "import ipywidgets as widgets\n",
    "\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from numpy import pi\n",
    "\n",
    "def f(n,noise_level):\n",
    "    prec = 100\n",
    "    nl = noise_level\n",
    "    #n = 10\n",
    "    T = pi\n",
    "    t = np.arange(0,T, T/prec)\n",
    "\n",
    "    noise = np.random.normal(0,nl,prec)\n",
    "\n",
    "    ###Change g_t to your own function!\n",
    "    g_t = 20*np.exp(-t/2)\n",
    "    ###\n",
    "    \n",
    "    g_t = g_t + noise\n",
    "    f_t = np.zeros(prec)\n",
    "    for k in range(n):\n",
    "        b = np.exp(-1j*k*t*(2*pi/T)) # k = n\n",
    "        c = np.inner(g_t,np.conj(b)*(1/(T)))\n",
    "        f_tc = f_t\n",
    "        f_t = f_t + c*b\n",
    "\n",
    "    plt.plot(t,g_t/np.linalg.norm(g_t),label='noisy function')\n",
    "    plt.plot(t,f_t.real/np.linalg.norm(f_t),label='generalized approx')\n",
    "    plt.legend()\n",
    "\n",
    "\n",
    "interact(f, n=widgets.IntSlider(min=2, max=1000, step=10, value=2),noise_level=widgets.FloatSlider(min=.00, max=.5, step=.05, value=.00))\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Challenge Problem\n",
    "\n",
    "Add a homework assignment that might take 10 minutes to complete.  Make sure you can work the problem yourself, but you do not need to submit a solution to the problem."
   ]
  },
  {
   "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.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}