{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "1559cf77-eb5b-4e56-8bd9-ae61a108fe92",
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "from setupFigure import SetupFigure\n",
    "from dftSlow import dft_coeff, dft_synthesis"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "b101d00c-d9a2-4222-879b-ac340f89f576",
   "metadata": {},
   "outputs": [],
   "source": [
    "def dft_fast_coeff(x):\n",
    "    \"\"\"\n",
    "    Evaluate Fourier coefficients using Numpy's fast Fourier transform.\n",
    "    This routine only returns the coefficients for the positive frequencies.\n",
    "    If N is even, it goes up to n=N/2.\n",
    "    If N is odd, it goes up to n=(N-1)/2.\n",
    "    :param x: array of function samples\n",
    "    \"\"\"\n",
    "    return np.fft.rfft(x, norm='forward')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "14467eda-e7f2-423d-9b78-b7cc9672f22c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def dft_fast_synthesis(fc, outnum='even'):\n",
    "    \"\"\"\n",
    "    Use numpy's fast Fourier synthesis taking only Fourier coefficients for positive frequencies\n",
    "    :param fc: aray of coefficients for positive frequencies only.\n",
    "    :param outnum: specifies if output time series has an even or odd number of samples (default: 'even')\n",
    "    \"\"\"\n",
    "    ns = 2*fc.size-2\n",
    "    if outnum == 'odd': ns = 2*fc.size-1\n",
    "    return np.fft.irfft(fc, ns, norm='forward')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "18ffbc73-5210-4b9a-b3e2-28d5aafd33d9",
   "metadata": {},
   "outputs": [],
   "source": [
    "def boxcar(dt, period, tup, tdown):\n",
    "    \"\"\"\n",
    "    Calculate samples of a boxcar function\n",
    "    :param dt: sampling interval\n",
    "    :param period: time range is 0 <= t < period (multiple of dt)\n",
    "    :param tup: time where boxcar goes from 0 to 1\n",
    "    :param tdown: time where boxcar goes from 1 to 0\n",
    "    \"\"\"\n",
    "    ns = int(period/dt)\n",
    "    t = dt*np.arange(0, ns)\n",
    "    return t, np.where(t < tup, 0, 1)*np.where(t > tdown, 0, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "3435e191-57a4-425a-9618-a7597b11916d",
   "metadata": {},
   "outputs": [],
   "source": [
    "def gaussian(dt, period, tmax, hwidth):\n",
    "    \"\"\"\n",
    "    Calculate samples of a Gaussian function\n",
    "    :param dt: sampling interval\n",
    "    :param period: time range is 0 <= t < period (multiple of dt)\n",
    "    :param tmax: time of maximum of Gaussian\n",
    "    :param hwidth: half width of Gaussian\n",
    "    \"\"\"\n",
    "    ns = int(period/dt)\n",
    "    t = dt*np.arange(0, ns)\n",
    "    return t, np.exp(-(t-tmax)**2/hwidth**2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a7e3fe9-484a-49cc-af35-03b532eb62cd",
   "metadata": {},
   "source": [
    "## Task 1: Compare \"slow\" Fourier transform with the fast version of numpy"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "971cc8b7-7ade-4755-b5ad-9cc83e66fed5",
   "metadata": {},
   "source": [
    "Again set up the boxcar function as in the previous assignment and use the provided functions to compute the Fourier coefficients by both methods. Verify that both methods yield the same results by printing the first 20 coefficients."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "f5da468c-fded-40b4-a691-6adc93fbf110",
   "metadata": {},
   "outputs": [],
   "source": [
    "t, bx = boxcar(1, 1000, 300, 500)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "fe9a16bd-1f5c-4a28-9b4b-ef7274c5f529",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1000 1000 501\n",
      "     0    2.010000e-01+0.000000e+00j   2.010000e-01+0.000000e+00j\n",
      "     1    -1.520194e-01-1.104485e-01j   -1.520194e-01-1.104485e-01j\n",
      "     2    4.686934e-02+1.442490e-01j   4.686934e-02+1.442490e-01j\n",
      "     3    3.108655e-02-9.567460e-02j   3.108656e-02-9.567460e-02j\n",
      "     4    -3.718483e-02+2.701637e-02j   -3.718484e-02+2.701636e-02j\n",
      "     5    -9.999970e-04+9.855762e-10j   -1.000000e-03+4.012337e-20j\n",
      "     6    2.587908e-02+1.880226e-02j   2.587908e-02+1.880225e-02j\n",
      "     7    -1.345747e-02-4.141783e-02j   -1.345747e-02-4.141783e-02j\n",
      "     8    -1.159567e-02+3.568779e-02j   -1.159566e-02+3.568778e-02j\n",
      "     9    1.615938e-02-1.174048e-02j   1.615938e-02-1.174048e-02j\n",
      "    10    1.000003e-03-1.096195e-09j   1.000000e-03-5.552491e-20j\n",
      "    11    -1.440952e-02-1.046912e-02j   -1.440952e-02-1.046913e-02j\n",
      "    12    7.887543e-03+2.427536e-02j   7.887542e-03+2.427536e-02j\n",
      "    13    7.096590e-03-2.184102e-02j   7.096579e-03-2.184102e-02j\n",
      "    14    -1.015033e-02+7.374651e-03j   -1.015033e-02+7.374646e-03j\n",
      "    15    -9.999993e-04-1.489762e-11j   -1.000000e-03-1.916335e-20j\n",
      "    16    1.010687e-02+7.343075e-03j   1.010687e-02+7.343074e-03j\n",
      "    17    -5.593138e-03-1.721391e-02j   -5.593137e-03-1.721390e-02j\n",
      "    18    -5.096129e-03+1.568428e-02j   -5.096130e-03+1.568428e-02j\n",
      "    19    7.302622e-03-5.305668e-03j   7.302625e-03-5.305668e-03j\n"
     ]
    }
   ],
   "source": [
    "c = dft_coeff(bx)                  # slow DFT\n",
    "fc = dft_fast_coeff(bx)            # fast DFT\n",
    "print(t.size, c.size, fc.size)\n",
    "for n in range(t.size//50):\n",
    "    print(\"{:6d}    {:15.6e}   {:15.6e}\".format(n, c[n], fc[n]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc4a9050-a171-479e-90a5-df7b924143a9",
   "metadata": {},
   "source": [
    "Also compare the slow and fast versions of Fourier synthesis by calling the provided functions. Print values at times around the discontinuities."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "a8abee97-2424-445d-81c3-98353d5ac270",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1000 1000\n",
      "   290   -1.053945e-07   -6.280549e-17\n",
      "   291   -8.330658e-08    1.449411e-17\n",
      "   292   -5.615582e-08   -1.440881e-16\n",
      "   293   -5.668051e-08   -1.612420e-16\n",
      "   294   -7.121410e-08    3.892420e-18\n",
      "   295   -8.654331e-08   -5.549463e-17\n",
      "   296   -1.116903e-07    4.558707e-18\n",
      "   297   -9.428106e-08    1.432295e-16\n",
      "   298   -1.238188e-07    3.231428e-17\n",
      "   299   -1.090360e-07    1.056715e-16\n",
      "   300    9.999999e-01    1.000000e+00\n",
      "   301    9.999999e-01    1.000000e+00\n",
      "   302    9.999999e-01    1.000000e+00\n",
      "   303    9.999999e-01    1.000000e+00\n",
      "   304    9.999999e-01    1.000000e+00\n",
      "   305    9.999999e-01    1.000000e+00\n",
      "   306    9.999999e-01    1.000000e+00\n",
      "   307    9.999999e-01    1.000000e+00\n",
      "   308    9.999999e-01    1.000000e+00\n",
      "   309    9.999999e-01    1.000000e+00\n",
      "   310    9.999999e-01    1.000000e+00\n"
     ]
    }
   ],
   "source": [
    "xr = dft_synthesis(c)\n",
    "if t.size%2 == 0:\n",
    "    xrf = dft_fast_synthesis(fc, outnum='even')\n",
    "else:\n",
    "    xrf = dft_fast_synthesis(fc, outnum='odd')\n",
    "print(xr.size, xrf.size)\n",
    "for k in range(290,311):\n",
    "    print(\"{:6d} {:15.6e} {:15.6e}\".format(k, xr[k], xrf[k]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1204f35f-7933-490e-94a7-af77edaca83c",
   "metadata": {},
   "source": [
    "## Task 2: Interpolation by appending zeros to the Fourier coefficients"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "accff2d0-77ce-4048-bad0-8396c62ef675",
   "metadata": {},
   "source": [
    "A band limited time series can be interpolated using a simple trick: First, the Fourier coefficients are computed up to the Nyquist frequency, $f_{Ny} = \\frac{N}{2}\\Delta f = \\frac{N}{2T}$. Then, $L$ zero coefficents are appended to increase the Nyquist frequency to $f'_{Ny} = (\\frac{N}{2}+L)\\Delta f$ and to decrease the sampling interval to $\\Delta t' = \\frac{1}{2f'_{Ny}}$. Subsequent Fourier synthesis produces an interpolated version of the original time series. These relations hold for even and odd number of samples.\n",
    "When doing the Fourier synthesis, the routine should be called with outnum='odd' for odd N and with outnum='even' for even N, respectively.\n",
    "\n",
    "First set up a Gaussian using the provided function. Then compute the Fourier coefficients. Print out the number of samples, the Nyquist frequency and the number of Fourier coefficients. For example, choose dt=5, period=100, tmax=50, hw=20. \n",
    "\n",
    "Second, append some zeros (20) to the array of coefficients and compute the new Nyquist frequency and the new sampling interval. Do the Fourier synthesis and print the new number of samples, the new Nyquist frequency and the new sampling interval.\n",
    "\n",
    "Third, plot the new and old time series into one graph."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "7c5663c1-5605-45df-8342-2dcefb2ec4b0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "N =  20  Nyquist frequency =  0.1  Ncoeff =  11\n"
     ]
    }
   ],
   "source": [
    "dt = 5; period = 100; tmax = 50; hw = 20; nz = 20\n",
    "t, g = gaussian(dt, period, tmax, hw)\n",
    "c = dft_fast_coeff(g)\n",
    "print(\"N = \", t.size,\" Nyquist frequency = \", t.size/(2*period),\" Ncoeff = \", c.size)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "2ad7ff56-8713-4386-9b4a-9228fb63fde0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "N =  60  Nyquist frequency =  0.3\n"
     ]
    }
   ],
   "source": [
    "cz = np.append(c, np.zeros(nz))\n",
    "fny = (t.size/2+nz)/period\n",
    "dtnew = 1./(2*fny)\n",
    "if t.size%2 != 0:\n",
    "    xr = dft_fast_synthesis(cz, outnum='odd')\n",
    "else:\n",
    "    xr = dft_fast_synthesis(cz, outnum='even')\n",
    "tz = dtnew*np.arange(0, xr.size)\n",
    "print(\"N = \", tz.size,\" Nyquist frequency = \", fny)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "2755badf-b7d1-47f0-ab3b-1a057faaf921",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x107e34350>]"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 55240 characters]",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig1, ax1 = SetupFigure(10, 5, \"Time [s]\", \"xr(t)\", \"Interpolated Gaussian\", 14)\n",
    "ax1.plot(tz, xr, color='blue', ls='-', lw=0.25, marker='d', markersize=4, zorder=10)\n",
    "ax1.plot(t, g, color='red', ls='', marker='o', markersize=6, zorder=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc9fd425-811c-49d1-8372-1fb2963a8310",
   "metadata": {},
   "source": [
    "## Task 3: Aliasing and the sampling theorem"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a1cf5112-c521-46d9-b642-4c3fbe3fa0ee",
   "metadata": {},
   "source": [
    "First, calculate values for a Gaussian with dt=0.25, period=100, tmax=50 and hwidth=1. Plot the Gaussian.\n",
    "\n",
    "Second, in a loop, calculate sme Gaussian with dt = 0.5, 1.0, 1.5 and 2.0. Compute the fast Fourier coefficients and the frequencies associated with them. Plot the absolute value of the coefficients into one graph. Set the upper frequency axis limit to 1.0 and use different colors for the curves. Compare the spectra, what do you observe?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "9e8596d7-1eb5-4248-85c5-e90ed8beaeb8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x107ea6c90>]"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 36356 characters]",
      "text/plain": [
       "<Figure size 1000x300 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "t, g = gaussian(0.25, 100, 50, 1)\n",
    "fig3, ax3 = SetupFigure(10, 3, \"Time [s]\", \"g(t)\", \"Sharp Gaussian\", 14)\n",
    "ax3.plot(t, g, color='blue', ls='-', lw=1.0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "83511cfe-45e9-4216-96c7-f932d6199034",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.legend.Legend at 0x107f681d0>"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 75140 characters]",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig2, ax2 = SetupFigure(10, 5, \"Frequency [s]\", \"G(f)\", \"Fourier coefficients\", 14)\n",
    "ax2.set_xlim(0, 1.0)\n",
    "col = ['grey', 'orange', 'red', 'magenta', 'blue']\n",
    "lw = [4, 1, 1, 1, 1]\n",
    "for i, dt in enumerate([0.25, 0.5, 1.0, 1.5, 2.0]):\n",
    "    t, g = gaussian(dt, 100, 50, 1)\n",
    "    c = dft_fast_coeff(g)\n",
    "    f = 1./period*np.arange(0, c.size)\n",
    "    ax2.plot(f, np.absolute(c), color=col[i], ls='-', lw=lw[i], label=\"DT = {:5.2f}\".format(dt))\n",
    "ax2.legend()    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b8b80cd5-d9b7-40e3-858c-ed2086167c37",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.12.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
