{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Overview of Noise Sources"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use this notebook to check the properties of the noise. The next few cells just contain a couple of convenience functions and <a href=\"#General-$1/f^{\\alpha}$-Power-Law-Noise\">you can skip directly to the content below</a>. \n",
    "\n",
    "You should have at least 8 GB of RAM if you want to run the whole notebook because all noise streams are kept in memory for analysis. The data rates below were measured on an <a href=\"https://ark.intel.com/content/www/us/en/ark/products/199339/intel-xeon-w-1270-processor-16m-cache-3-40-ghz.html\">Intel Xeon W-1270</a>."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import pyplnoise\n",
    "import time\n",
    "import numpy as np\n",
    "import scipy.signal\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def autocorr(x):\n",
    "    corr_raw = scipy.signal.fftconvolve(x, x[::-1], mode='full')\n",
    "    corr_raw = corr_raw[corr_raw.size//2:]\n",
    "    # scale the result to get an unbiased estimate\n",
    "    result = np.divide(corr_raw, corr_raw.size - np.arange(0, corr_raw.size, 1))\n",
    "    return result\n",
    "def plot_welch_and_autocorr(x, fs, nperseg=250000, ac_lag_max=6000):\n",
    "    ac = autocorr(x)\n",
    "    f, S_f = scipy.signal.welch(x, fs=fs, nperseg=nperseg,\n",
    "                                scaling='density', return_onesided=False,\n",
    "                                detrend='constant')\n",
    "    fig, axs = plt.subplots(1, 2, figsize=(14,5))\n",
    "    fig.suptitle(\"Welch Periodogram & Autocorrelation\")\n",
    "    axs[0].grid(True)\n",
    "    axs[0].loglog(f[2:f.size//2], S_f[2:f.size//2])\n",
    "    axs[0].set_xlabel(\"f (Hz)\")\n",
    "    axs[0].set_ylabel(\"Two-sided power spectral density (unit$^2$/Hz)\")\n",
    "    axs[1].plot(ac[0:ac_lag_max])\n",
    "    axs[1].grid(True)\n",
    "    axs[1].yaxis.tick_right()\n",
    "    axs[1].yaxis.set_label_position('right')\n",
    "    axs[1].set_xlabel(\"Lag $j$\")\n",
    "    axs[1].set_ylabel(\"Autocorrelation $<x_{i}x_{i-j}>$\")\n",
    "    return (fig, axs)\n",
    "def plot_time_series_and_histogram(t, x):\n",
    "    fig, axs = plt.subplots(1, 2, figsize=(14,5))\n",
    "    fig.suptitle(\"Time series & Histogram\")\n",
    "    axs[0].grid(True)\n",
    "    axs[0].plot(t/3600., x)\n",
    "    axs[0].set_xlabel(\"Time (h)\")\n",
    "    axs[0].set_ylabel(\"Signal (a.u.)\")\n",
    "    n, bins, _ = axs[1].hist(x, 40)\n",
    "    axs[1].plot(bins, n.max()*np.exp(-(bins-x.mean())**2./(2.*x.std()**2.)))\n",
    "    axs[1].grid(True)\n",
    "    axs[1].yaxis.tick_right()\n",
    "    axs[1].yaxis.set_label_position('right')\n",
    "    axs[1].set_xlabel(\"Signal (a.u.)\")\n",
    "    axs[1].set_ylabel(\"# Samples\")\n",
    "    return (fig, axs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "fs = 100.\n",
    "npts = 60000000\n",
    "t = np.linspace(0., npts/fs, num=npts)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  General $1/f^{\\alpha}$ Power Law Noise"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Parameters $\\alpha=1.5$, $f_{min} = 10^{-2}\\text{ Hz}$, $f_{max} = 10\\text{ Hz}$. As you can see, the noise becomes white below $f_{min}$ and above $f_{max}$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Data rate: 1.3793e+07 samples/sec\n"
     ]
    }
   ],
   "source": [
    "plnoise = pyplnoise.AlphaNoise(fs, 1e-2, 10., alpha=1.5)\n",
    "t0 = time.time()\n",
    "x_pl = plnoise.get_series(npts)\n",
    "t1 = time.time()\n",
    "print(\"Data rate: {:g} samples/sec\".format(npts/(t1 - t0)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 37664 characters]\n",
      "text/plain": [
       "<Figure size 1008x360 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plot_welch_and_autocorr(x_pl, fs, nperseg=600000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 36608 characters]\n",
      "text/plain": [
       "<Figure size 1008x360 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plot_time_series_and_histogram(t, x_pl)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pink Noise"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A convenience alias is provided for the special case $\\alpha=1.0$. Here: $f_{min} = 10^{-2}\\text{ Hz}$, $f_{max} = 50\\text{ Hz}$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "pknoise = pyplnoise.PinkNoise(fs, 1e-2, 50.)\n",
    "x_pk = pknoise.get_series(npts)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 35108 characters]\n",
      "text/plain": [
       "<Figure size 1008x360 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plot_welch_and_autocorr(x_pk, fs, nperseg=600000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 36788 characters]\n",
      "text/plain": [
       "<Figure size 1008x360 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plot_time_series_and_histogram(t, x_pk)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Red Noise"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Parameter $f_{min} = 10^{-2}\\text{ Hz}$. If you also like to adjust an upper frequency limit, use ``AlphaNoise`` with $\\alpha = 2.0$ (note however that ``AlphaNoise`` is slower than the ``RedNoise`` class)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Data rate: 6.17235e+07 samples/sec\n"
     ]
    }
   ],
   "source": [
    "rdnoise = pyplnoise.RedNoise(fs, 1e-2)\n",
    "t0 = time.time()\n",
    "x_rd = rdnoise.get_series(npts)\n",
    "t1 = time.time()\n",
    "print(\"Data rate: {:g} samples/sec\".format(npts/(t1 - t0)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 39876 characters]\n",
      "text/plain": [
       "<Figure size 1008x360 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plot_welch_and_autocorr(x_rd, fs, nperseg=600000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 39464 characters]\n",
      "text/plain": [
       "<Figure size 1008x360 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plot_time_series_and_histogram(t, x_rd)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## White Noise"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "White noise is the basis for everything and is generated using NumPy's default random number generator. Starting with NumPy version 1.17, it is of the family of <a href=\"http://www.pcg-random.org\">\"permuted congruential generators\"</a>."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Data rate: 1.04429e+08 samples/sec\n"
     ]
    }
   ],
   "source": [
    "whnoise = pyplnoise.WhiteNoise(fs, psd=1.0)\n",
    "t0 = time.time()\n",
    "x_wh = whnoise.get_series(npts)\n",
    "t1 = time.time()\n",
    "print(\"Data rate: {:g} samples/sec\".format(npts/(t1 - t0)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 53624 characters]\n",
      "text/plain": [
       "<Figure size 1008x360 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plot_welch_and_autocorr(x_wh, fs, nperseg=10000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 36504 characters]\n",
      "text/plain": [
       "<Figure size 1008x360 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plot_time_series_and_histogram(t, x_wh)"
   ]
  }
 ],
 "metadata": {},
 "nbformat": 4,
 "nbformat_minor": 2
}
