{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Postselection: exit and panic\n", "\n", "**Download this notebook - {nb-download}`postselect.ipynb`**\n", "\n", "\n", "In this example we will look at two ways of ending a Guppy program early:\n", "\n", "1. `exit` will end the current shot and carry on with subsequent ones. We will use this to\n", " implement postselection.\n", "2. `panic` is used to signal some unexpected error and as such it will end the shot and\n", " not run any subsequent ones." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from typing import Counter\n", "\n", "from guppylang import guppy\n", "from guppylang.std.builtins import result, array, exit, panic\n", "from guppylang.std.quantum import measure_array, measure, qubit, h, cx\n", "from guppylang.defs import GuppyFunctionDefinition\n", "\n", "from selene_sim import DepolarizingErrorModel\n", "from selene_sim.exceptions import SelenePanicError" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Postselection\n", "\n", "We can use postselection to implement fault tolerant state preparation for the [[7, 1,\n", "3]] [Steane code](https://en.wikipedia.org/wiki/Steane_code).\n", "\n", "Let's first define our \"Steane qubit\" as a struct containing our 7 data qubits, then\n", "write a function to prepare an encoded $|0\\rangle$ non-fault tolerantly. We use the\n", "preparation circuit from [_Realization of real-time fault-tolerant quantum error correction_](https://arxiv.org/abs/2107.07505).\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "@guppy.struct\n", "class SteaneQubit:\n", " data_qs: array[qubit, 7]\n", "\n", "\n", "@guppy\n", "def non_ft_zero() -> SteaneQubit:\n", " data_qubits = array(qubit() for _ in range(7))\n", " plus_ids = array(0, 4, 6)\n", " for i in plus_ids:\n", " h(data_qubits[i])\n", "\n", " cx_pairs = array((0, 1), (4, 5), (6, 3), (6, 5), (4, 2), (0, 3), (4, 1), (3, 2))\n", " for c, t in cx_pairs:\n", " cx(data_qubits[c], data_qubits[t])\n", " return SteaneQubit(data_qubits)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now implement fault-tolerant preparation using postselection. We can use an\n", "ancilla to check the prepared state, and if we detect an error use `exit` to end the\n", "shot with a message about why we exited." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "@guppy\n", "def ft_zero() -> SteaneQubit:\n", " q = non_ft_zero()\n", " ancilla = qubit()\n", " flags = array(1, 3, 5)\n", " for f in flags:\n", " cx(q.data_qs[f], ancilla)\n", " if measure(ancilla):\n", " exit(\"Postselected: FT prep failed\", 1)\n", " return q" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's define a couple of utility functions - a Guppy function to check the parity of a\n", "bit array, and a python function to run our program and report the results.\n", "\n", "We use a simple depolarizing error model to induce errors in the preparation." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "n = guppy.nat_var(\"n\")\n", "\n", "\n", "@guppy\n", "def parity_check(data_bits: array[bool, n]) -> bool:\n", " out = False\n", " for i in range(n):\n", " out ^= data_bits[i]\n", " return out\n", "\n", "\n", "error_model = DepolarizingErrorModel(\n", " random_seed=1234,\n", " # single qubit gate error rate\n", " p_1q=1e-3,\n", " # two qubit gate error rate\n", " p_2q=1e-3,\n", " # set state preparation and measurement error rates to 0\n", " p_meas=0,\n", " p_init=0,\n", ")\n", "\n", "\n", "def run(main_def: GuppyFunctionDefinition) -> Counter:\n", " res = (\n", " main_def.emulator(n_qubits=8)\n", " .stabilizer_sim()\n", " .with_seed(42)\n", " .with_shots(1000)\n", " .with_error_model(error_model)\n", " .run()\n", " )\n", "\n", " return res.collated_counts()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now define our `main` program and run it. We know that all basis states of the\n", "encoded Steane $|0\\rangle$ state have a $0$ parity, so we can use that to verify our preparation." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Counter({(('parity', '0'),): 968,\n", " (('exit: Postselected: FT prep failed', '1'),): 23,\n", " (('parity', '1'),): 9})" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "@guppy\n", "def main() -> None:\n", " steane_q = ft_zero()\n", "\n", " # Measure the data qubits\n", " data = measure_array(steane_q.data_qs)\n", " result(\"parity\", parity_check(data))\n", "\n", "\n", "run(main)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see, the state preparation succeeded in 968 cases out of 1000.\n", "In 23 of the unsuccessful cases, an error was detected on the ancillas and was\n", "discarded through postselection. In the remaining 9 cases, the state failed\n", "in a way that was not detected by postselection but was instead detected by a\n", "parity check performed after measuring all of the qubits.\n", "\n", "Note the result tag in the discarded shot is prefixed with `exit: ` followed\n", "by the specified message, and the value of the result entry is the error code\n", "(1 in this case)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Panic\n", "\n", "The `panic` function is similar to `exit` but is used for exceptional circumstances -\n", "when something unexpected has gone wrong. For example we could define a physical\n", "hadamard function that takes an index to act on the data qubit array. If the index is\n", "out of bounds, we can panic with a helpful message. This will raise an error during the\n", "simulation, and no subsequent shots will be run." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "tags": [ "raises-exception" ] }, "outputs": [ { "ename": "SelenePanicError", "evalue": "Panic (#1001): Invalid data index in physical_h", "output_type": "error", "traceback": [ "\u001b[31m---------------------------------------------------------------------------\u001b[39m", "\u001b[31mSelenePanicError\u001b[39m Traceback (most recent call last)", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[6]\u001b[39m\u001b[32m, line 18\u001b[39m\n\u001b[32m 15\u001b[39m data = measure_array(steane_q.data_qs)\n\u001b[32m 16\u001b[39m result(\u001b[33m\"\u001b[39m\u001b[33mparity\u001b[39m\u001b[33m\"\u001b[39m, parity_check(data))\n\u001b[32m---> \u001b[39m\u001b[32m18\u001b[39m \u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmain\u001b[49m\u001b[43m)\u001b[49m\n", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[4]\u001b[39m\u001b[32m, line 31\u001b[39m, in \u001b[36mrun\u001b[39m\u001b[34m(main_def)\u001b[39m\n\u001b[32m 24\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mrun\u001b[39m(main_def: GuppyFunctionDefinition) -> Counter:\n\u001b[32m 25\u001b[39m res = (\n\u001b[32m 26\u001b[39m \u001b[43mmain_def\u001b[49m\u001b[43m.\u001b[49m\u001b[43memulator\u001b[49m\u001b[43m(\u001b[49m\u001b[43mn_qubits\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m8\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m 27\u001b[39m \u001b[43m \u001b[49m\u001b[43m.\u001b[49m\u001b[43mstabilizer_sim\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 28\u001b[39m \u001b[43m \u001b[49m\u001b[43m.\u001b[49m\u001b[43mwith_seed\u001b[49m\u001b[43m(\u001b[49m\u001b[32;43m42\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m 29\u001b[39m \u001b[43m \u001b[49m\u001b[43m.\u001b[49m\u001b[43mwith_shots\u001b[49m\u001b[43m(\u001b[49m\u001b[32;43m1000\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m 30\u001b[39m \u001b[43m \u001b[49m\u001b[43m.\u001b[49m\u001b[43mwith_error_model\u001b[49m\u001b[43m(\u001b[49m\u001b[43merror_model\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m---> \u001b[39m\u001b[32m31\u001b[39m \u001b[43m \u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 32\u001b[39m )\n\u001b[32m 34\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m res.collated_counts()\n", "\u001b[36mFile \u001b[39m\u001b[32m/app/docs/.venv/lib/python3.11/site-packages/guppylang/emulator/instance.py:211\u001b[39m, in \u001b[36mEmulatorInstance.run\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 207\u001b[39m result_stream = \u001b[38;5;28mself\u001b[39m._run_instance()\n\u001b[32m 209\u001b[39m \u001b[38;5;66;03m# TODO progress bar on consuming iterator?\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m211\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mEmulatorResult\u001b[49m\u001b[43m(\u001b[49m\u001b[43mresult_stream\u001b[49m\u001b[43m)\u001b[49m\n", "\u001b[36mFile \u001b[39m\u001b[32m/app/docs/.venv/lib/python3.11/site-packages/guppylang/emulator/result.py:70\u001b[39m, in \u001b[36mEmulatorResult.__init__\u001b[39m\u001b[34m(self, results)\u001b[39m\n\u001b[32m 67\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m__init__\u001b[39m(\n\u001b[32m 68\u001b[39m \u001b[38;5;28mself\u001b[39m, results: Iterable[QsysShot | Iterable[TaggedResult]] | \u001b[38;5;28;01mNone\u001b[39;00m = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 69\u001b[39m ):\n\u001b[32m---> \u001b[39m\u001b[32m70\u001b[39m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m.\u001b[49m\u001b[34;43m__init__\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mresults\u001b[49m\u001b[43m=\u001b[49m\u001b[43mresults\u001b[49m\u001b[43m)\u001b[49m\n", "\u001b[36mFile \u001b[39m\u001b[32m/app/docs/.venv/lib/python3.11/site-packages/hugr/qsystem/result.py:156\u001b[39m, in \u001b[36mQsysResult.__init__\u001b[39m\u001b[34m(self, results)\u001b[39m\n\u001b[32m 153\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m__init__\u001b[39m(\n\u001b[32m 154\u001b[39m \u001b[38;5;28mself\u001b[39m, results: Iterable[QsysShot | Iterable[TaggedResult]] | \u001b[38;5;28;01mNone\u001b[39;00m = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 155\u001b[39m ):\n\u001b[32m--> \u001b[39m\u001b[32m156\u001b[39m \u001b[38;5;28mself\u001b[39m.results = \u001b[43m[\u001b[49m\n\u001b[32m 157\u001b[39m \u001b[43m \u001b[49m\u001b[43mres\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43misinstance\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mres\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mQsysShot\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mQsysShot\u001b[49m\u001b[43m(\u001b[49m\u001b[43mres\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mres\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mresults\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43m[\u001b[49m\u001b[43m]\u001b[49m\n\u001b[32m 158\u001b[39m \u001b[43m \u001b[49m\u001b[43m]\u001b[49m\n", "\u001b[36mFile \u001b[39m\u001b[32m/app/docs/.venv/lib/python3.11/site-packages/hugr/qsystem/result.py:157\u001b[39m, in \u001b[36m\u001b[39m\u001b[34m(.0)\u001b[39m\n\u001b[32m 153\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m__init__\u001b[39m(\n\u001b[32m 154\u001b[39m \u001b[38;5;28mself\u001b[39m, results: Iterable[QsysShot | Iterable[TaggedResult]] | \u001b[38;5;28;01mNone\u001b[39;00m = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 155\u001b[39m ):\n\u001b[32m 156\u001b[39m \u001b[38;5;28mself\u001b[39m.results = [\n\u001b[32m--> \u001b[39m\u001b[32m157\u001b[39m res \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(res, QsysShot) \u001b[38;5;28;01melse\u001b[39;00m \u001b[43mQsysShot\u001b[49m\u001b[43m(\u001b[49m\u001b[43mres\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mfor\u001b[39;00m res \u001b[38;5;129;01min\u001b[39;00m results \u001b[38;5;129;01mor\u001b[39;00m []\n\u001b[32m 158\u001b[39m ]\n", "\u001b[36mFile \u001b[39m\u001b[32m/app/docs/.venv/lib/python3.11/site-packages/hugr/qsystem/result.py:61\u001b[39m, in \u001b[36mQsysShot.__init__\u001b[39m\u001b[34m(self, entries)\u001b[39m\n\u001b[32m 60\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m__init__\u001b[39m(\u001b[38;5;28mself\u001b[39m, entries: Iterable[TaggedResult] | \u001b[38;5;28;01mNone\u001b[39;00m = \u001b[38;5;28;01mNone\u001b[39;00m):\n\u001b[32m---> \u001b[39m\u001b[32m61\u001b[39m \u001b[38;5;28mself\u001b[39m.entries = \u001b[38;5;28mlist\u001b[39m(entries \u001b[38;5;129;01mor\u001b[39;00m [])\n", "\u001b[36mFile \u001b[39m\u001b[32m/app/docs/.venv/lib/python3.11/site-packages/selene_sim/result_handling/parse_shot.py:154\u001b[39m, in \u001b[36mparse_shot\u001b[39m\u001b[34m(parser, event_hook, parse_results, stdout_file, stderr_file)\u001b[39m\n\u001b[32m 152\u001b[39m \u001b[38;5;66;03m# pass panic errors to the caller\u001b[39;00m\n\u001b[32m 153\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m SelenePanicError \u001b[38;5;28;01mas\u001b[39;00m panic:\n\u001b[32m--> \u001b[39m\u001b[32m154\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m panic\n\u001b[32m 155\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m SeleneRuntimeError \u001b[38;5;28;01mas\u001b[39;00m error:\n\u001b[32m 156\u001b[39m error.stdout = stdout_file.read_text()\n", "\u001b[36mFile \u001b[39m\u001b[32m/app/docs/.venv/lib/python3.11/site-packages/selene_sim/result_handling/parse_shot.py:139\u001b[39m, in \u001b[36mparse_shot\u001b[39m\u001b[34m(parser, event_hook, parse_results, stdout_file, stderr_file)\u001b[39m\n\u001b[32m 137\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m parse_results:\n\u001b[32m 138\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m parsed.code >= \u001b[32m1000\u001b[39m:\n\u001b[32m--> \u001b[39m\u001b[32m139\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m SelenePanicError(\n\u001b[32m 140\u001b[39m message=parsed.message,\n\u001b[32m 141\u001b[39m code=parsed.code,\n\u001b[32m 142\u001b[39m stdout=stdout_file.read_text(),\n\u001b[32m 143\u001b[39m stderr=stderr_file.read_text(),\n\u001b[32m 144\u001b[39m )\n\u001b[32m 145\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 146\u001b[39m \u001b[38;5;28;01myield\u001b[39;00m (\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mexit: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mparsed.message\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m, parsed.code)\n", "\u001b[31mSelenePanicError\u001b[39m: Panic (#1001): Invalid data index in physical_h" ] } ], "source": [ "@guppy\n", "def physical_h(q: SteaneQubit, data_idx: int) -> None:\n", " if data_idx >= 7:\n", " panic(\"Invalid data index in physical_h\")\n", " h(q.data_qs[data_idx])\n", "\n", "\n", "@guppy\n", "def main() -> None:\n", " steane_q = ft_zero()\n", "\n", " # add a physical H gate\n", " physical_h(steane_q, 8)\n", "\n", " data = measure_array(steane_q.data_qs)\n", " result(\"parity\", parity_check(data))\n", "\n", "run(main)\n" ] } ], "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 }