{ "cells": [ { "cell_type": "markdown", "id": "4f76a9f9", "metadata": {}, "source": [ "# Amplitude estimation\n", "\n", "**Download Notebook** - {nb-download}`qae.ipynb`\n", "\n", "*Prepare-and-measure* (PaM) amplitude estimation." ] }, { "cell_type": "markdown", "id": "3242dd45", "metadata": {}, "source": [ "Given a state-preparation unitary $A$ that flags a \"good\" subspace on a distinguished\n", "*target* qubit,\n", "\n", "$$A\\ket{0} = \\sqrt{1-a}\\,\\ket{\\psi_0}\\ket{0}_{\\text{target}} + \\sqrt{a}\\,\\ket{\\psi_1}\\ket{1}_{\\text{target}},$$\n", "\n", "the square of the amplitude $\\sqrt a$ is the probability $a \\in [0, 1] \\subset \\mathbb{R}$ of measuring the target in $\\ket{1}$. Note that the above equation allows for the target qubit to be entangled with the rest of the system.\n", "\n", "The **prepare-and-measure** estimator is the simplest possible amplitude estimator: it\n", "prepares $A\\ket{0}$, measures the target, and repeats. The estimate is just the fraction\n", "of $\\ket{1}$ outcomes,\n", "\n", "$$\\hat{a} = \\frac{\\text{count}(1)}{\\text{repeat}} \\;\\xrightarrow[\\text{repeat}\\to\\infty]{}\\; a.$$\n", "\n", "For any repeat $\\geq 1$, $\\hat a$ is a random variable who's expectation value is $a$, with standard deviation (= estimation error) $\\sqrt{a(1-a)/\\text{repeat}}$. This carries **no** quantum speedup\n", "(it is equivalent to classical Monte Carlo sampling), but it is a useful baseline and a\n", "building block for the more advanced amplitude estimators.\n", "\n", "`guppyalgos.algorithms.amplitude_amplification.qae` exposes two composable Guppy functions:\n", "\n", "- `prepare_and_measure_once(state_prep)` → `bool` — prepare $A\\ket{0}$ and\n", " measure the target **once**;\n", "- `prepare_and_measure(state_prep, repeat)` → `float` — repeat that\n", " `repeat` times and return the estimate.\n", "\n", "The state-preparation unitary $A$ is supplied as a higher-order argument, and qubit\n", "allocation is handled internally, so both functions drop straight into a larger\n", "workflow." ] }, { "cell_type": "code", "execution_count": 1, "id": "7666042a", "metadata": { "execution": { "iopub.execute_input": "2026-07-21T16:14:40.809015Z", "iopub.status.busy": "2026-07-21T16:14:40.808914Z", "iopub.status.idle": "2026-07-21T16:14:42.650335Z", "shell.execute_reply": "2026-07-21T16:14:42.649834Z" } }, "outputs": [], "source": [ "import numpy as np\n", "from guppylang import guppy\n", "from guppylang.std.angles import angle\n", "from guppylang.std.builtins import array, comptime, result\n", "from guppylang.std.quantum import cx, h, qubit, ry\n", "\n", "from guppyalgos.algorithms.amplitude_amplification.qae import prepare_and_measure, prepare_and_measure_once" ] }, { "cell_type": "markdown", "id": "8b09ec75", "metadata": {}, "source": [ "## Define a state preparation and estimate its amplitude\n", "\n", "We use a toy $A$ that rotates the target with an `ry` gate so that\n", "$P(\\text{target}=1) = \\sin^2(\\pi t / 2)$, and puts the register into an independent\n", "superposition (so the input is genuinely a superposition, while the target marginal is\n", "exactly the amplitude). `guppylang`'s `angle` is in half-turns, i.e. `1.0` corresponds\n", "to $\\pi$ radians.\n", "\n", "The runnable program is a zero-argument `main` that calls the estimator and records the\n", "returned float with `result`." ] }, { "cell_type": "code", "execution_count": 2, "id": "07fb5ee0", "metadata": { "execution": { "iopub.execute_input": "2026-07-21T16:14:42.651513Z", "iopub.status.busy": "2026-07-21T16:14:42.651402Z", "iopub.status.idle": "2026-07-21T16:14:45.003117Z", "shell.execute_reply": "2026-07-21T16:14:45.002698Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "estimate = 0.2430 analytic a = 0.2500\n" ] } ], "source": [ "n_register = 2\n", "angle_half_turns = 1.0 / 3.0 # a = sin^2(pi * t / 2) = 0.25\n", "repeat = 4000\n", "\n", "\n", "@guppy\n", "def state_prep(register: array[qubit, comptime(n_register)], target: qubit) -> None:\n", " # A|0>: rotate the target, place the register in a uniform superposition\n", " ry(target, angle(comptime(angle_half_turns)))\n", " for i in range(comptime(n_register)):\n", " h(register[i])\n", "\n", "\n", "@guppy\n", "def main() -> None:\n", " result(\"estimate\", prepare_and_measure(state_prep, comptime(repeat)))\n", "\n", "\n", "shots = main.emulator(n_qubits=n_register + 1).with_seed(1).with_shots(1).run()\n", "estimate = shots.collated_shots()[0][\"estimate\"][0]\n", "analytic = float(np.sin(np.pi * angle_half_turns / 2) ** 2)\n", "print(f\"estimate = {estimate:.4f} analytic a = {analytic:.4f}\")" ] }, { "cell_type": "markdown", "id": "cb7dacad", "metadata": {}, "source": [ "## Entangled target\n", "\n", "Nothing changes when the target is *entangled* with the register rather than in a\n", "product state. Here `ry` rotates `register[0]` and a `cx` copies it onto the target,\n", "producing $\\sqrt{1-a}\\,\\ket{00} + \\sqrt{a}\\,\\ket{11}$. Measuring the target still yields\n", "$\\ket{1}$ with probability $a$." ] }, { "cell_type": "code", "execution_count": 3, "id": "429bfcea", "metadata": { "execution": { "iopub.execute_input": "2026-07-21T16:14:45.004225Z", "iopub.status.busy": "2026-07-21T16:14:45.004140Z", "iopub.status.idle": "2026-07-21T16:14:48.235393Z", "shell.execute_reply": "2026-07-21T16:14:48.233878Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "entangled estimate = 0.2485 analytic a = 0.2500\n" ] } ], "source": [ "@guppy\n", "def entangled_prep(register: array[qubit, comptime(1)], target: qubit) -> None:\n", " # Entangle the target with the register: sqrt(1-a)|00> + sqrt(a)|11>\n", " ry(register[0], angle(comptime(angle_half_turns)))\n", " cx(register[0], target)\n", "\n", "\n", "@guppy\n", "def main_entangled() -> None:\n", " result(\"estimate\", prepare_and_measure(entangled_prep, comptime(repeat)))\n", "\n", "\n", "shots = main_entangled.emulator(n_qubits=2).with_seed(2).with_shots(1).run()\n", "estimate = shots.collated_shots()[0][\"estimate\"][0]\n", "print(f\"entangled estimate = {estimate:.4f} analytic a = {analytic:.4f}\")" ] }, { "cell_type": "markdown", "id": "e9b9a925", "metadata": {}, "source": [ "## The single-shot primitive\n", "\n", "`prepare_and_measure` is just a loop over `prepare_and_measure_once`, which returns a\n", "single target measurement. Recording that per shot and averaging classically over the\n", "emulator's shots reproduces the same estimate — useful when you want the raw\n", "outcomes (e.g. to feed a different estimator)." ] }, { "cell_type": "code", "execution_count": 4, "id": "a335b601", "metadata": { "execution": { "iopub.execute_input": "2026-07-21T16:14:48.237398Z", "iopub.status.busy": "2026-07-21T16:14:48.237308Z", "iopub.status.idle": "2026-07-21T16:14:50.503878Z", "shell.execute_reply": "2026-07-21T16:14:50.503180Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "fraction of |1> over 4000 shots = 0.2325\n" ] } ], "source": [ "@guppy\n", "def single_shot() -> None:\n", " result(\"outcome\", prepare_and_measure_once(state_prep))\n", "\n", "\n", "n_shots = 4000\n", "shots = (\n", " single_shot.emulator(n_qubits=n_register + 1).with_seed(3).with_shots(n_shots).run()\n", ")\n", "outcomes = [s[\"outcome\"][0] for s in shots.collated_shots()]\n", "print(f\"fraction of |1> over {n_shots} shots = {sum(outcomes) / n_shots:.4f}\")" ] }, { "cell_type": "markdown", "id": "e8f3a11a", "metadata": {}, "source": [ "## Convergence\n", "\n", "The sampling error shrinks like $\\sqrt{a(1-a)/\\text{repeat}}$, so more repetitions give\n", "a tighter estimate. Note this is the classical Monte Carlo $1/\\sqrt{N}$ scaling —\n", "the quantum-accelerated amplitude estimators (which reuse the same $A$) improve on it." ] }, { "cell_type": "code", "execution_count": 5, "id": "bd061824", "metadata": { "execution": { "iopub.execute_input": "2026-07-21T16:14:50.505363Z", "iopub.status.busy": "2026-07-21T16:14:50.505268Z", "iopub.status.idle": "2026-07-21T16:14:56.320339Z", "shell.execute_reply": "2026-07-21T16:14:56.319846Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "repeat= 50 estimate=0.2800 |error|=0.0300 ~std=0.0612\n", "repeat= 500 estimate=0.2560 |error|=0.0060 ~std=0.0194\n", "repeat= 5000 estimate=0.2558 |error|=0.0058 ~std=0.0061\n" ] } ], "source": [ "for r in [50, 500, 5000]:\n", "\n", " @guppy\n", " def converge() -> None:\n", " result(\"estimate\", prepare_and_measure(state_prep, comptime(r)))\n", "\n", " est = (\n", " converge.emulator(n_qubits=n_register + 1)\n", " .with_seed(0)\n", " .with_shots(1)\n", " .run()\n", " .collated_shots()[0][\"estimate\"][0]\n", " )\n", " std = float(np.sqrt(analytic * (1 - analytic) / r))\n", " print(f\"repeat={r:5d} estimate={est:.4f} |error|={abs(est - analytic):.4f} ~std={std:.4f}\")" ] } ], "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.13.9" } }, "nbformat": 4, "nbformat_minor": 5 }