{ "cells": [ { "cell_type": "markdown", "id": "title", "metadata": {}, "source": [ "# Comparator-based Rz synthesis with the Steane architecture\n", "\n", "**Download this notebook - {nb-download}`comparator_rz_steane.ipynb`**\n", "\n", "> ⚠️ **Warning**\n", ">\n", "> Comparator-based synthesis combined with Steane encoding requires a large number of physical gates, even for simple programs. Running programs produced by this workflow on current hardware will likely exceed practical runtime limits and may result in timeout errors.\n", "\n", "The Steane architecture supports Clifford+$T$ operations, but not arbitrary $Rz$ rotations. This notebook demonstrates using {py:class}`~guppyft.decompose.ComparatorRzDecomposer` to approximate $Rz$ gates with a comparator-based decomposition, then encodes the resulting Clifford+$T$ program using the Steane architecture. The method uses a repeat-until-success subroutine that can be used on arbitrary angles $\\theta$ at runtime and is based on the paper [\"Single-qubit rotation algorithm with logarithmic Toffoli count and gate depth\"](https://arxiv.org/pdf/2404.05618).\n", "\n", "The comparator decomposition uses $2\\lceil\\log_2(1 / \\varepsilon)\\rceil$ ancilla qubits and introduces Toffoli gates. Its probability of success on each $Rz$ decomposition attempt is greater than $1/2$; a shot is discarded if any of the $Rz$ gates in the program fail to be decomposed after the specified number of attempts (`max_attempts`).\n", "\n", "To learn more about the comparator decomposition, see the {external+guppyalgos:doc}`Comparator-based Rz synthesis ` example in `guppyalgos`." ] }, { "cell_type": "markdown", "id": "program-heading", "metadata": {}, "source": [ "## Define a program with a runtime rotation\n", "\n", "The angle is generated at runtime to demonstrate that `ComparatorRzDecomposer` can decompose both static and runtime angles. Applying the angle and its inverse returns the target to $\\ket{+}$; a final Hadamard and computational-basis measurement should therefore return `0`." ] }, { "cell_type": "code", "execution_count": 1, "id": "imports-and-program", "metadata": { "ExecuteTime": { "end_time": "2026-09-16T12:48:48.076961Z", "start_time": "2026-09-16T12:48:47.331410Z" }, "execution": { "iopub.execute_input": "2026-09-15T20:32:10.983980Z", "iopub.status.busy": "2026-09-15T20:32:10.983818Z", "iopub.status.idle": "2026-09-15T20:32:11.532212Z", "shell.execute_reply": "2026-09-15T20:32:11.531790Z" } }, "outputs": [], "source": [ "from guppylang import guppy\n", "from guppylang.std.platform import output\n", "from guppylang.std.qsystem.random import RNG\n", "from guppylang.std.quantum import h, measure, qubit, rz\n", "\n", "\n", "@guppy\n", "def random_rz() -> None:\n", " rng = RNG(123)\n", " theta = rng.random_angle()\n", " rng.discard()\n", "\n", " target = qubit()\n", " h(target)\n", " rz(target, theta)\n", " rz(target, -theta)\n", " h(target)\n", " output(\"result\", measure(target).read())" ] }, { "cell_type": "markdown", "id": "decomposition-heading", "metadata": {}, "source": [ "## Decompose before encoding\n", "\n", "`ComparatorRzDecomposer` replaces every `Rz` with its comparator-based decomposition, which adds Toffoli gates. {py:class}`~guppyft.decompose.ToffoliDecomposer` must therefore run next, converting each Toffoli to Clifford+$T$. The resulting package can then be encoded by the Steane architecture." ] }, { "cell_type": "code", "execution_count": 2, "id": "decompose", "metadata": { "ExecuteTime": { "end_time": "2026-09-16T12:48:49.514496Z", "start_time": "2026-09-16T12:48:48.077746Z" }, "execution": { "iopub.execute_input": "2026-09-15T20:32:11.533434Z", "iopub.status.busy": "2026-09-15T20:32:11.533329Z", "iopub.status.idle": "2026-09-15T20:32:12.896557Z", "shell.execute_reply": "2026-09-15T20:32:12.896087Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Comparator ancillas: 8\n" ] } ], "source": [ "from guppyft.decompose import ComparatorRzDecomposer, ToffoliDecomposer\n", "\n", "# Compile the package with minimal optimization\n", "# to avoid `rz` angles being squashed\n", "pkg = random_rz.with_minimal_opt().compile()\n", "rz_decomposer = ComparatorRzDecomposer(\n", " epsilon=0.1, max_attempts=15\n", ")\n", "rz_decomposer.then(ToffoliDecomposer()).run(pkg.modules[0], inplace=True)\n", "\n", "print(f\"Comparator ancillas: {rz_decomposer.n_ancillas()}\")" ] }, { "cell_type": "markdown", "id": "encoding-heading", "metadata": {}, "source": [ "## Encode and emulate with the Steane architecture\n", "\n", "The comparator ancillas need to be encoded as well, so the Steane architecture needs one logical block for each of them. It also needs one block for the target qubit and one workspace block for magic-state injection. Each Steane block occupies seven physical qubits; the additional three physical qubits are used as flag ancillas during state preparation.\n", "\n", "The `Coinflip` simulator is sufficient to exercise the compilation and control flow, but does not simulate the quantum state. We set the `bias=0.0` in the simulator to force all measurements to be 0 to select the success branch during the comparator synthesis and, in the case of the encoded program, to ensure all RUS state preparations succeed. We attach a `CircuitExtractor` to the emulator to compare the two-qubit depth before and after Steane encoding.\n", "\n", "Even at the relaxed precision used here, the forward-and-inverse pair expands to a large number of physical two-qubit gates. This makes comparator-based synthesis combined with Steane encoding prohibitive on current hardware: the circuit is likely to exceed practical runtime limits, and accumulated two-qubit gate error would dominate the result. Use this workflow to study fault-tolerant resource costs rather than as a near-term hardware implementation." ] }, { "cell_type": "code", "execution_count": 3, "id": "encode-and-emulate", "metadata": { "ExecuteTime": { "end_time": "2026-09-16T12:48:55.671472Z", "start_time": "2026-09-16T12:48:49.549974Z" }, "execution": { "iopub.execute_input": "2026-09-15T20:32:12.897597Z", "iopub.status.busy": "2026-09-15T20:32:12.897543Z", "iopub.status.idle": "2026-09-15T20:32:18.618560Z", "shell.execute_reply": "2026-09-15T20:32:18.618157Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Two-qubit depth:\n", "\tBefore Steane encoding: 32\n", "\tAfter Steane encoding: 1139\n" ] } ], "source": [ "from guppyft.code.steane.encode import SteaneBuilder\n", "from guppylang.emulator import EmulatorBuilder\n", "from selene_sim.backends.bundled_simulators import Coinflip\n", "from selene_sim.event_hooks.instruction_log import CircuitExtractor\n", "\n", "logger = CircuitExtractor()\n", "(\n", " EmulatorBuilder()\n", " .build(pkg, n_qubits=1 + rz_decomposer.n_ancillas())\n", " .with_simulator(Coinflip(bias=0.0)) # Set bias=0.0 to select success branches\n", " .with_event_hook(logger)\n", ").run()\n", "unencoded_two_qubit_depth = (\n", " logger.shots[0].get_user_circuit().depth_2q()\n", ")\n", "\n", "steane = SteaneBuilder().build(n_blocks=10)\n", "results = (\n", " steane.emulator(pkg, n_qubits=73)\n", " .with_simulator(Coinflip(bias=0.0)) # Set bias=0.0 to select success branches\n", " .with_event_hook(logger)\n", " .run()\n", ")\n", "encoded_two_qubit_depth = (\n", " logger.shots[1].get_user_circuit().depth_2q()\n", ")\n", "\n", "print(\"Two-qubit depth:\")\n", "print(f\"\\tBefore Steane encoding: {unencoded_two_qubit_depth}\")\n", "print(f\"\\tAfter Steane encoding: {encoded_two_qubit_depth}\")" ] } ], "metadata": { "kernelspec": { "display_name": "guppyft (3.14.0)", "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.12.12" } }, "nbformat": 4, "nbformat_minor": 5 }