{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# N-qubit GHZ and Graph state preparation\n", "\n", "**Download this notebook - {nb-download}`ghz_and_graph.ipynb`**\n", "\n", "In this example we prepare n-qubit GHZ and graph states and perform stabilizer\n", "simulation using Stim. To do this we make use of the fact that Guppy can load in values\n", "from the host python program and inject them in to the resulting Guppy program as constants." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import networkx as nx\n", "from collections import Counter\n", "from guppylang import guppy\n", "from guppylang.defs import GuppyFunctionDefinition\n", "from guppylang.std.builtins import array, comptime, result\n", "from guppylang.std.quantum import cx, cz, h, measure_array, qubit\n", "from guppylang.emulator import EmulatorResult" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We would like to write one Guppy function for any number of qubits, but we want the\n", "number of qubits to be known at Guppy compile time so we can check the number of qubits\n", "and avoid using dynamic classical or quantum allocation (which can be slow and induce\n", "memory error).\n", "\n", "To achieve this we can define a Guppy function that is _generic_ over the size of an array,\n", "because arrays have sizes known at compile time." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# Declare generic variable\n", "n = guppy.nat_var(\"n\")\n", "\n", "\n", "# define guppy function generic over array size\n", "@guppy\n", "def build_ghz_state(q: array[qubit, n]) -> None:\n", " h(q[0])\n", " # array size argument used in range to produce statically sized array\n", " for i in range(n - 1):\n", " cx(q[i], q[i + 1])" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def build_ghz_prog(n_qb: int) -> GuppyFunctionDefinition:\n", " \"\"\"Build a Guppy program that prepares a GHZ state on `n_qb` qubits.\"\"\"\n", "\n", " # we can define the entry point to the guppy program dependent on\n", " # the number of qubits we want to use.\n", "\n", " @guppy\n", " def main() -> None:\n", " # allocate number of qubits specified from outer\n", " # python function using a `comptime` expression.\n", " q = array(qubit() for _ in range(comptime(n_qb)))\n", "\n", " build_ghz_state(q)\n", "\n", " result(\"c\", measure_array(q))\n", "\n", " # return the guppy function\n", " return main" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's define a quick utility to help us read out our results as bitstring counts." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "def get_counts(shots: EmulatorResult) -> Counter[str]:\n", " \"\"\"Counter treating all results from a shot as entries in a single bitstring\"\"\"\n", " counter_list = []\n", " for shot in shots:\n", " for e in shot:\n", " bitstring = \"\".join(str(k) for k in e[1])\n", " counter_list.append(bitstring)\n", "\n", " return Counter(counter_list)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now run our GHZ prep, we expect to see an even mix of |0..0> and |1..1>." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Counter({'000000': 51, '111111': 49})" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ghz_prog = build_ghz_prog(6)\n", "shots = ghz_prog.emulator(n_qubits=6).stabilizer_sim().with_seed(2).with_shots(100).run()\n", "get_counts(shots)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Similarly, we can define a [graph state](https://en.wikipedia.org/wiki/Graph_state) over\n", "an arbitrary graph by first using `networkx` to define our graph as a list of edges, and\n", "loading those edge pairs in to Guppy.\n", "\n", "Because the Guppy compiler knows the length of the list being pulled in, it can\n", "load it in as a statically sized array (just like the qubit array)." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "def build_graph_state(graph: nx.Graph) -> GuppyFunctionDefinition:\n", " edges = list(graph.edges)\n", " n_qb = graph.number_of_nodes()\n", "\n", " @guppy\n", " def main() -> None:\n", " qs = array(qubit() for _ in range(comptime(n_qb)))\n", "\n", " for i in range(len(qs)):\n", " h(qs[i])\n", "\n", " for i, j in comptime(edges):\n", " # apply CZ along every graph edge\n", " cz(qs[i], qs[j])\n", "\n", " result(\"c\", measure_array(qs))\n", "\n", " return main" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's test out our graph state builder with a $K_3$ complete graph over 3 nodes. We\n", "expect to see an even mix of all 3 qubit basis states when measured." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[('000', 12),\n", " ('001', 14),\n", " ('010', 15),\n", " ('011', 10),\n", " ('100', 16),\n", " ('101', 9),\n", " ('110', 13),\n", " ('111', 11)]" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "k3_graph = nx.complete_graph(3)\n", "\n", "graph_prog = build_graph_state(k3_graph)\n", "\n", "shots = graph_prog.emulator(n_qubits=3).stabilizer_sim().with_seed(2).with_shots(100).run()\n", "sorted(get_counts(shots).items())" ] } ], "metadata": { "kernelspec": { "display_name": "venv", "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.11.2" } }, "nbformat": 4, "nbformat_minor": 4 }