{ "cells": [ { "cell_type": "markdown", "id": "intro", "metadata": {}, "source": [ "# Transversal gates\n", "\n", "**Download Notebook** - {nb-download}`transversal.ipynb`\n", "\n", "`transversal` applies an operation across a quantum register, or between corresponding qubits in two registers. It keeps repeated gate application concise and makes the intended register wiring clear.\n", "\n", "This is a higher-order function: you supply the gate itself, such as `x` or `cx`, as an argument. Run the cells in order to explore both forms." ] }, { "cell_type": "code", "execution_count": null, "id": "imports", "metadata": {}, "outputs": [], "source": [ "from guppylang import guppy\n", "from guppylang.std.builtins import array, output\n", "from guppylang.std.quantum import collect_measurements, cx, measure_array, x\n", "\n", "from guppyalgos.utils import transversal, qarray\n", "\n", "n_qubits = 5" ] }, { "cell_type": "markdown", "id": "one-qubit", "metadata": {}, "source": [ "## Apply a one-qubit gate\n", "\n", "For a one-qubit operation $U$, applying it to every qubit of an $n$-qubit register gives\n", "\n", "$$\n", "U_{\\mathrm{all}}=U^{\\otimes n}.\n", "$$\n", "\n", "The call `transversal(x, qreg)` applies X to each qubit. Starting from zero, every qubit becomes one:\n", "\n", "$$\n", "|0\\rangle^{\\otimes n}\\xrightarrow{X^{\\otimes n}}|1\\rangle^{\\otimes n}.\n", "$$\n", "\n", "The measured result below is therefore always `11111`." ] }, { "cell_type": "code", "execution_count": null, "id": "single", "metadata": {}, "outputs": [], "source": [ "@guppy\n", "def single_qubit_example() -> None:\n", " qreg = qarray(n_qubits)\n", " transversal(x, qreg)\n", " output(\"bits\", collect_measurements(measure_array(qreg)))\n", "\n", "\n", "print(single_qubit_example.emulator(n_qubits).stabilizer_sim().run().collated_counts())" ] }, { "cell_type": "markdown", "id": "two-qubit", "metadata": {}, "source": [ "## Apply a two-qubit gate\n", "\n", "With two registers, `transversal` applies the gate to matching indices:\n", "\n", "$$\n", "V_{\\mathrm{pairs}}=\\prod_{i=0}^{n-1}V_{c_i,t_i}.\n", "$$\n", "\n", "- Both registers must have the same size.\n", "- For `cx`, the first register supplies the controls and the second supplies the targets.\n", "- Each control interacts with its matching target, rather than every target.\n", "\n", "On computational-basis inputs, the action is\n", "\n", "$$\n", "|c\\rangle_C|t\\rangle_T\n", "\\longmapsto\n", "|c\\rangle_C|t\\oplus c\\rangle_T,\n", "$$\n", "\n", "where $\\oplus$ is bitwise XOR. Here the controls are prepared as all ones, so the initially zero targets also become all ones." ] }, { "cell_type": "code", "execution_count": null, "id": "paired", "metadata": {}, "outputs": [], "source": [ "@guppy\n", "def two_qubit_example() -> None:\n", " control_qreg = qarray(n_qubits)\n", " target_qreg = qarray(n_qubits)\n", " transversal(x, control_qreg)\n", " transversal(cx, control_qreg, target_qreg)\n", " output(\"controls\", collect_measurements(measure_array(control_qreg)))\n", " output(\"targets\", collect_measurements(measure_array(target_qreg)))\n", "\n", "\n", "print(two_qubit_example.emulator(2 * n_qubits).stabilizer_sim().run().collated_counts())" ] }, { "cell_type": "markdown", "id": "overloads", "metadata": {}, "source": [ "## One name, six overloads\n", "\n", "The library registers six implementations with `@guppy.overload`. At compile time, guppy resolves a call from the gate's signature and the supplied arguments.\n", "\n", "| Scope | One-qubit gate | Two-qubit gate |\n", "| --- | --- | --- |\n", "| Every index | `transversal(x, qreg)` | `transversal(cx, control_qreg, target_qreg)` |\n", "| Exclude indices | `transversal(x, qreg, array(1, 3))` | `transversal(cx, control_qreg, target_qreg, array(1, 3))` |\n", "| Index range | `transversal(x, qreg, 2, 4)` | `transversal(cx, control_qreg, target_qreg, 2, 4)` |\n", "\n", "All indices are zero-based. The register arguments are borrowed, so they remain available for subsequent gates or measurement.\n", "\n", "### Exclude selected indices\n", "\n", "The array argument contains indices to **skip**. For a five-qubit register, `array(1, 3)` applies the gate at indices 0, 2, and 4. For a two-qubit gate, it skips the corresponding pairs.\n", "\n", "### Apply only within a range\n", "\n", "The range includes its start and excludes its end: `2, 4` applies only at indices 2 and 3. Keep the bounds within the register width.\n", "\n", "The complete example below uses every overload in sequence. Each operation acts on the state left by the preceding operation." ] }, { "cell_type": "code", "execution_count": null, "id": "combined", "metadata": {}, "outputs": [], "source": [ "@guppy\n", "def main() -> None:\n", " control_qreg = qarray(n_qubits)\n", " target_qreg = qarray(n_qubits)\n", "\n", " # Apply at every index.\n", " transversal(x, control_qreg)\n", " transversal(cx, control_qreg, target_qreg)\n", "\n", " # Skip indices 1 and 3; swap control and target roles for this CX layer.\n", " transversal(x, target_qreg, array(1, 3))\n", " transversal(cx, target_qreg, control_qreg, array(1, 3))\n", "\n", " # Apply only at indices 2 and 3.\n", " transversal(x, control_qreg, 2, 4)\n", " transversal(cx, control_qreg, target_qreg, 2, 4)\n", "\n", " output(\"controls\", collect_measurements(measure_array(control_qreg)))\n", " output(\"targets\", collect_measurements(measure_array(target_qreg)))\n", "\n", "\n", "print(main.emulator(2 * n_qubits).stabilizer_sim().with_seed(2).run().collated_counts())" ] }, { "cell_type": "markdown", "id": "results", "metadata": {}, "source": [ "## Read the result\n", "\n", "All gates in this example act deterministically on computational-basis states. After the final layer:\n", "\n", "- The control qubits at indices 0, 1, and 4 are one; indices 2 and 3 are zero.\n", "- Target indices 1 and 3 are one; the other target qubits are zero.\n", "\n", "The explicit `output` calls record the measured bitstrings for inspection. The stabilizer simulator is sufficient here because X and CX are Clifford gates.\n", "\n", "Try replacing X with H in the first example to prepare a superposition. Applying CX from that register to a zero register then prepares one Bell pair per matching index." ] } ], "metadata": { "kernelspec": { "display_name": "guppyalgos (3.13.3)", "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.13.3" } }, "nbformat": 4, "nbformat_minor": 5 }