{
"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",
"
\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",
"
"
]
}
],
"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
}