{ "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}$. Maybe we need a one-dimensional line whose vertices can\n", "represent each of the states. We also know that qubits can be in an equal superposition states: $\\ket{+}$ and $\\ket{-}$. This now forces us to extend our 1D line to a 2D Cartesian coordinate system. If you dive deeper you will learn about the existence of states like \n", "$\\ket{+i}$ and $\\ket{-i}$, this calls for a 3D extension.\n", "\n", "It turns out that a sphere is able to depict all the possible states of a single qubit. This is called a Bloch sphere. \n", "\n", "\"Bloch\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": null, "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, installing. Please restart your kernel after this is done.\")\n", " !{sys.executable} -m pip install qutip\\>=5 matplotlib\\>=3.5\n", " print(\"\\nNew libraries have been installed. Please restart your kernel!\")\n" ] }, { "cell_type": "code", "execution_count": null, "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": null, "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": null, "metadata": {}, "outputs": [], "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": null, "metadata": {}, "outputs": [], "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": null, "metadata": {}, "outputs": [], "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": null, "metadata": {}, "outputs": [], "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": null, "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": null, "metadata": {}, "outputs": [], "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": null, "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": null, "metadata": {}, "outputs": [], "source": [ "print(cudaq.draw(kernel_to_draw))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "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", "\"Circuit" ] } ], "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 }