{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "%matplotlib inline"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Transfer Functions\n\nThis example demonstrates how to visualize the related transfer functions of\nthe analog system and digital estimator.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\nimport numpy as np\nimport cbadc"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Chain-of-Integrators ADC Example\n\n\n<img src=\"file://images/chainOfIntegratorsGeneral.svg\" width=\"500\" align=\"center\" alt=\"The chain of integrators ADC.\">\n\nIn this example, we will use the chain-of-integrators ADC analog system for\ndemonstrational purposes. However, except for the analog system creation,\nthe steps for a generic analog system and digital estimator.\n\nFor in-depth details regarding the chain-of-integrators transfer function\nsee,\n`chain-of-integrators <https://www.research-collection.ethz.ch/bitstream/handle/20.500.11850/469192/control-bounded_converters_a_dissertation_by_hampus_malmberg.pdf?sequence=1&isAllowed=y#page=97/>`_\n\nFirst we will import dependent modules and initialize a chain-of-integrators\nsetup. With the following analog system parameters\n\n- $\\beta = \\beta_1 = \\dots = \\beta_N = 6250$\n- $\\rho_1 = \\dots = \\rho_N = - 0.02$\n- $\\kappa_1 = \\dots = \\kappa_N = - 1$\n- $N = 6$\n\nnote that $\\mathbf{C}^\\mathsf{T}$ is automatically assumed an identity\nmatrix of size $N\\times N$.\n\nUsing the :py:class:`cbadc.analog_system.ChainOfIntegrators` class which\nderives from the main analog system class\n:py:class:`cbadc.analog_system.AnalogSystem`.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "# We fix the number of analog states.\nN = 6\n# Set the amplification factor.\nbeta = 6250.\nrho = - 0.02\nkappa = - 1.0\n# In this example, each nodes amplification and local feedback will be set\n# identically.\nbetaVec = beta * np.ones(N)\nrhoVec = betaVec * rho\nkappaVec = kappa * beta * np.eye(N)\n\n# Instantiate a chain-of-integrators analog system.\nanalog_system = cbadc.analog_system.ChainOfIntegrators(betaVec, rhoVec, kappaVec)\n# print the system matrices.\nprint(analog_system)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Plotting the Analog System's Transfer Function\n\nNext, we plot the transfer function of the analog system\n\n$\\mathbf{G}(\\omega) = \\begin{pmatrix}G_1(\\omega), \\dots, G_N(\\omega)\\end{pmatrix}^\\mathsf{T} = \\mathbf{C}^\\mathsf{T} \\left(i \\omega \\mathbf{I}_N - \\mathbf{A}\\right)^{-1}\\mathbf{B}$\n\nusing the class method\n:func:`cbadc.analog_system.AnalogSystem.transfer_function_matrix`.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "# Logspace frequencies\nfrequencies = np.logspace(-3, 0, 500)\nomega = 4 * np.pi * beta * frequencies\n\n# Compute transfer functions for each frequency in frequencies\ntransfer_function = analog_system.transfer_function_matrix(omega)\ntransfer_function_dB = 20 * np.log10(np.abs(transfer_function))\n\n# For each output 1,...,N compute the corresponding tranfer function seen\n# from the input.\nfor n in range(N):\n    plt.semilogx(\n        frequencies, transfer_function_dB[n, 0, :], label=f\"$G_{n+1}(\\omega)$\")\n\n# Add the norm ||G(omega)||_2\nplt.semilogx(\n    frequencies,\n    20 * np.log10(np.linalg.norm(\n        transfer_function[:, 0, :],\n        axis=0)),\n    '--',\n    label=\"$ ||\\mathbf{G}(\\omega)||_2 $\")\n\n# Add labels and legends to figure\nplt.legend()\nplt.grid(which='both')\nplt.title(\"Transfer functions, $G_1(\\omega), \\dots, G_N(\\omega)$\")\nplt.xlabel(\"$\\omega / (4 \\pi \\\\beta ) $\")\nplt.ylabel(\"dB\")\nplt.xlim((frequencies[0], frequencies[-1]))\nplt.gcf().tight_layout()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Plotting the Estimator's Signal and Noise Transfer Function\n\nTo determine the estimate's signal and noise transfer function, we must\ninstantiate a digital estimator\n:py:class:`cbadc.digital_estimator.DigitalEstimator`. The bandwidth of the\ndigital estimation filter is mainly determined by the parameter\n$\\eta^2$ as the noise transfer function (NTF) follows as\n\n$\\text{NTF}( \\omega) = \\mathbf{G}( \\omega)^\\mathsf{H} \\left(\n\\mathbf{G}( \\omega)\\mathbf{G}( \\omega)^\\mathsf{H} + \\eta^2 \\mathbf{I}_N\n\\right)^{-1}$\n\nand similarly, the signal transfer function (STF) follows as\n\n$\\text{STF}( \\omega) = \\text{NTF}( \\omega) \\mathbf{G}( \\omega)$.\n\nWe compute these two by invoking the class methods\n:func:`cbadc.digital_estimator.DigitalEstimator.noise_transfer_function` and\n:func:`cbadc.digital_estimator.DigitalEstimator.signal_transfer_function`\nrespectively.\n\nthe digital estimator requires us to also instantiate a digital control\n:py:class:`cbadc.digital_control.DigitalControl`.\n\nFor the chain-of-integrators example, the noise transfer function\nresults in a row vector\n$\\text{NTF}(\\omega) = \\begin{pmatrix} \\text{NTF}_1(\\omega), \\dots,\n\\text{NTF}_N(\\omega)\\end{pmatrix} \\in \\mathbb{C}^{1 \\times \\tilde{N}}$\nwhere $\\text{NTF}_\\ell(\\omega)$ refers to the noise transfer function\nfrom the $\\ell$-th observation to the final estimate.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "# Define dummy control and control sequence (not used when computing transfer\n# functions). However necessary to instantiate the digital estimator\n\nT = 1/(2 * beta)\ndigital_control = cbadc.digital_control.DigitalControl(T, N)\n\n\n# Compute eta2 for a given bandwidth.\nomega_3dB = (4 * np.pi * beta) / 100.\neta2 = np.linalg.norm(analog_system.transfer_function_matrix(\n    np.array([omega_3dB])).flatten()) ** 2\n\n# Instantiate estimator.\ndigital_estimator = cbadc.digital_estimator.DigitalEstimator(\n    analog_system, digital_control, eta2, K1=1)\n\n# Compute NTF\nntf = digital_estimator.noise_transfer_function(omega)\nntf_dB = 20 * np.log10(np.abs(ntf))\n\n# Compute STF\nstf = digital_estimator.signal_transfer_function(omega)\nstf_dB = 20 * np.log10(np.abs(stf.flatten()))\n\n\n# Plot\nplt.figure()\nplt.semilogx(frequencies, stf_dB, label='$STF(\\omega)$')\nfor n in range(N):\n    plt.semilogx(frequencies, ntf_dB[0, n, :], label=f\"$|NTF_{n+1}(\\omega)|$\")\nplt.semilogx(frequencies, 20 * np.log10(np.linalg.norm(\n    ntf[0, :, :], axis=0)), '--', label=\"$ || NTF(\\omega) ||_2 $\")\n\n# Add labels and legends to figure\nplt.legend()\nplt.grid(which='both')\nplt.title(\"Signal and noise transfer functions\")\nplt.xlabel(\"$\\omega / (4 \\pi \\\\beta ) $\")\nplt.ylabel(\"dB\")\nplt.xlim((frequencies[0], frequencies[-1]))\nplt.gcf().tight_layout()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Setting the Bandwidth of the Estimation Filter\n\nFinally, we will investigate the effect of eta2 on the STF and NTF.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "# create a vector of etas to be evaluated,\neta2_vec = np.logspace(0, 10, 11)[::2]\n\nplt.figure()\nfor eta2 in eta2_vec:\n    # Instantiate an estimator for each eta.\n    digital_estimator = cbadc.digital_estimator.DigitalEstimator(\n        analog_system, digital_control, eta2, K1=1)\n    # Compute stf and ntf\n    ntf = digital_estimator.noise_transfer_function(omega)\n    ntf_dB = 20 * np.log10(np.abs(ntf))\n    stf = digital_estimator.signal_transfer_function(omega)\n    stf_dB = 20 * np.log10(np.abs(stf.flatten()))\n\n    # Plot\n    color = next(plt.gca()._get_lines.prop_cycler)['color']\n    plt.semilogx(frequencies, 20 *\n                 np.log10(np.linalg.norm(ntf[0, :, :], axis=0)),\n                 '--', color=color)\n    plt.semilogx(frequencies, stf_dB,\n                 label=f\"$\\eta^2={10 * np.log10(eta2):0.0f} dB$\", color=color)\n\n# Add labels and legends to figure\nplt.legend(loc='lower left')\nplt.grid(which='both')\nplt.title(\"$|G(\\omega)|$ - solid, $||\\mathbf{H}(\\omega)||_2$ - dashed\")\nplt.xlabel(\"$\\omega / (4 \\pi \\\\beta ) $\")\nplt.ylabel(\"dB\")\nplt.xlim((3e-3, 1))\nplt.ylim((-240, 20))\nplt.gcf().tight_layout()"
      ]
    }
  ],
  "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.5"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}