{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Visualization\n",
    "\n",
    "## Qubit Visualization\n",
    "\n",
    "What are the possible states a qubit can be in and how can we build up a visual cue to help us make sense of quantum states and their evolution?\n",
    "\n",
    "We know our qubit can have two distinct states: $\\ket{0}$ and $\\ket{1}$. If these were the only two states, we could represent them as two vectors on a one-dimensional line (i.e., the z-axis in the image below). We also know that qubits can be in an equal superposition of states: $\\ket{+}$ and $\\ket{-}$. In order to capture all of the states in equal superposition, we will need a 2D plane (i.e., the $xy$-plane in the image below). If you dive deeper you will learn about the existence of other states that will call for a 3D extension. \n",
    "\n",
    "In general, a quantum state can be written in the form $\\ket{\\psi} = \\cos(\\frac{\\theta}{2})\\ket{0}+e^{i\\varphi}\\sin(\\frac{\\theta}{2})\\ket{1}$ where $\\theta$ is a real number between $0$ and $\\pi$ and $\\varphi$ is a real value between $0$ and $2\\pi$.  For example, the minus state, $\\ket{-} = \\frac{1}{\\sqrt{2}}\\ket{0}- \\frac{1}{\\sqrt{2}}\\ket{1}$, can be rewritten as\n",
    "$$\\ket{-}  = \\cos(\\frac{\\theta}{2})\\ket{0}+e^{i\\varphi}\\sin(\\frac{\\theta}{2})\\ket{1}\\text{ with }\\theta = \\frac{\\pi}{2}\\text{ and }\\varphi = \\pi.$$ \n",
    "This can be visualized in the image below as a unit vector pointing in the direction of the negative $x$-axis.\n",
    "\n",
    "Using spherical coordinates, it is possible to depict all the possible states of a single qubit on a sphere. This is called a Bloch sphere. \n",
    "\n",
    "<img src=\"../images/Bloch_sphere.png\" alt=\"Bloch Sphere\" width=\"300\" height=\"300\">\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let us try to showcase the functionality to render such a 3D representation with CUDA-Q. \n",
    "First, let us define a single-qubit kernel that returns a different state each time. This kernel uses random rotations.\n",
    "\n",
    "Note: CUDA-Q uses the [QuTiP](https://qutip.org) library to render Bloch spheres. The following code will throw an error if QuTiP is not installed. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# install `qutip` in the current Python kernel. Skip this if `qutip` is already installed.\n",
    "# `matplotlib` is required for all visualization tasks.\n",
    "# Make sure to restart your kernel if you execute this!\n",
    "# In a Jupyter notebook, go to the menu bar > Kernel > Restart Kernel.\n",
    "# In VSCode, click on the Restart button in the Jupyter toolbar.\n",
    "\n",
    "# The '\\' before the '>' operator is so that the shell does not misunderstand\n",
    "# the '>' qualifier for the bash pipe operation.\n",
    "\n",
    "import sys\n",
    "\n",
    "try:\n",
    "    import matplotlib.pyplot as plt\n",
    "    import qutip\n",
    "\n",
    "except ImportError:\n",
    "    print(\"Tools not found, please install and restart your kernel after this is done.\")\n",
    "    #!{sys.executable} -m pip install qutip\\>5 matplotlib\\>=3.5\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "import cudaq\n",
    "import numpy as np\n",
    "\n",
    "## Retry the subsequent cells by setting the target to density matrix simulator.\n",
    "# cudaq.set_target(\"density-matrix-cpu\")\n",
    "\n",
    "\n",
    "@cudaq.kernel\n",
    "def kernel(angles: np.ndarray):\n",
    "    qubit = cudaq.qubit()\n",
    "    rz(angles[0], qubit)\n",
    "    rx(angles[1], qubit)\n",
    "    rz(angles[2], qubit)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, we instantiate a random number generator, so we can get random outputs. We then create 4 random single-qubit states by using `cudaq.add_to_bloch_sphere()` on the output state obtained from the random kernel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "rng = np.random.default_rng(seed=11)\n",
    "blochSphereList = []\n",
    "for _ in range(4):\n",
    "    angleList = rng.random(3) * 2 * np.pi\n",
    "    sph = cudaq.add_to_bloch_sphere(cudaq.get_state(kernel, angleList))\n",
    "    blochSphereList.append(sph)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can display the spheres with `cudaq.show()`. Show the first sphere:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 135432 characters]",
      "text/plain": [
       "<Figure size 500x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "cudaq.show(blochSphereList[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also show multiple Bloch spheres side by side - simply set the `nrows` and `ncols` in the call to `cudaq.show()` accordingly. Make sure to have more spaces than spheres in your list, else it will throw an error! Let us show two spheres in a row:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 101280 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "cudaq.show(blochSphereList[:2], nrows=1, ncols=2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can show them in a column too, if we want! Simply set the `nrows = 2` and `ncols = 1`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 73872 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "cudaq.show(blochSphereList[:2], nrows=2, ncols=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Can we show the entire list of 4 Bloch spheres we created? Absolutely!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 143292 characters]",
      "text/plain": [
       "<Figure size 640x480 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "cudaq.show(blochSphereList[:], nrows=2, ncols=2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What if we had to add multiple vectors to a single Bloch sphere? CUDA-Q uses the [QuTiP](https://www.qutip.org) toolbox to construct Bloch spheres. We can then add multiple states to the same Bloch sphere by passing the sphere object as an argument to `cudaq.add_to_bloch_sphere()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "import qutip\n",
    "\n",
    "rng = np.random.default_rng(seed=47)\n",
    "blochSphere = qutip.Bloch()\n",
    "for _ in range(10):\n",
    "    angleList = rng.random(3) * 2 * np.pi\n",
    "    sph = cudaq.add_to_bloch_sphere(cudaq.get_state(kernel, angleList), blochSphere)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This created a single Bloch sphere with 10 random vectors. Let us see how it looks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 157716 characters]",
      "text/plain": [
       "<Figure size 500x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "blochSphere.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Unfortunately, there is no such handy visualization for multi-qubit states. In particular, a multi-qubit state cannot be visualized as multiple Bloch spheres due to the nature of entanglement that makes quantum computing so powerful. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Kernel Visualization\n",
    "\n",
    "A CUDA-Q kernel can be visualized using the `cudaq.draw` API which returns a string representing the drawing of the execution path, in the specified format. ASCII (default) and LaTeX formats are supported."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "@cudaq.kernel\n",
    "def kernel_to_draw():\n",
    "    q = cudaq.qvector(4)\n",
    "    h(q)\n",
    "    x.ctrl(q[0], q[1])\n",
    "    y.ctrl([q[0], q[1]], q[2])\n",
    "    z(q[2])\n",
    "    \n",
    "    swap(q[0], q[1])\n",
    "    swap(q[0], q[3])\n",
    "    swap(q[1], q[2])\n",
    "\n",
    "    r1(3.14159, q[0])\n",
    "    tdg(q[1])\n",
    "    s(q[2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "     ╭───╮                  ╭───────────╮       \n",
      "q0 : ┤ h ├──●────●────╳───╳─┤ r1(3.142) ├───────\n",
      "     ├───┤╭─┴─╮  │    │   │ ╰───────────╯╭─────╮\n",
      "q1 : ┤ h ├┤ x ├──●────╳───┼───────╳──────┤ tdg ├\n",
      "     ├───┤╰───╯╭─┴─╮╭───╮ │       │      ╰┬───┬╯\n",
      "q2 : ┤ h ├─────┤ y ├┤ z ├─┼───────╳───────┤ s ├─\n",
      "     ├───┤     ╰───╯╰───╯ │               ╰───╯ \n",
      "q3 : ┤ h ├────────────────╳─────────────────────\n",
      "     ╰───╯                                      \n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(cudaq.draw(kernel_to_draw))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\\documentclass{minimal}\n",
      "\\usepackage{quantikz}\n",
      "\\begin{document}\n",
      "\\begin{quantikz}\n",
      "  \\lstick{$q_0$} & \\gate{H} & \\ctrl{1} & \\ctrl{2} & \\swap{1} & \\swap{3} & \\gate{R_1(3.142)} & \\qw & \\qw \\\\\n",
      "  \\lstick{$q_1$} & \\gate{H} & \\gate{X} & \\ctrl{1} & \\targX{} & \\qw & \\swap{1} & \\gate{T^\\dag} & \\qw \\\\\n",
      "  \\lstick{$q_2$} & \\gate{H} & \\qw & \\gate{Y} & \\gate{Z} & \\qw & \\targX{} & \\gate{S} & \\qw \\\\\n",
      "  \\lstick{$q_3$} & \\gate{H} & \\qw & \\qw & \\qw & \\targX{} & \\qw & \\qw & \\qw \\\\\n",
      "\\end{quantikz}\n",
      "\\end{document}\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(cudaq.draw('latex', kernel_to_draw))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Copy this output string into any LaTeX editor and export it to PDF.\n",
    "\n",
    "<img src=\"../images/circuit_pdf.png\" alt=\"Circuit in PDF format\" width=\"500\" \n",
    "height=\"250\">\n",
    "\n"
   ]
  }
 ],
 "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.10.12"
  },
  "vscode": {
   "interpreter": {
    "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
