From 705944ba3da5542431d01210adaf6931e4610e20 Mon Sep 17 00:00:00 2001 From: Spencer Churchill <25377399+splch@users.noreply.github.com> Date: Tue, 17 Mar 2026 21:51:21 -0700 Subject: [PATCH 1/5] add interactive WebAssembly quantum textbook Replace Jupyter notebooks with a browser-native interactive textbook powered by Goqu compiled to WebAssembly. Readers edit OpenQASM code and see circuit diagrams, histograms, and Bloch spheres rendered live in 712 lines of Go/HTML/CSS/JS with zero external dependencies. --- .gitignore | 4 + Makefile | 13 +- README.md | 4 +- notebooks/01-the-qubit.ipynb | 597 ------- notebooks/02-single-qubit-gates.ipynb | 921 ---------- notebooks/03-measurement.ipynb | 795 --------- notebooks/04-multi-qubit-gates.ipynb | 1367 -------------- notebooks/05-entanglement.ipynb | 977 ----------- notebooks/06-teleportation.ipynb | 914 ---------- notebooks/07-quantum-fourier-transform.ipynb | 1167 ------------ notebooks/08-phase-estimation.ipynb | 616 ------- notebooks/08b-textbook-algorithms.ipynb | 954 ---------- notebooks/09-grovers-search.ipynb | 1321 -------------- notebooks/10-shors-algorithm.ipynb | 1660 ------------------ notebooks/10b-quantum-counting.ipynb | 698 -------- notebooks/11-noise-and-decoherence.ipynb | 1226 ------------- notebooks/11b-quantum-error-correction.ipynb | 1642 ----------------- notebooks/12-transpilation.ipynb | 1439 --------------- notebooks/13-variational-algorithms.ipynb | 1370 --------------- notebooks/14-quantum-machine-learning.ipynb | 1034 ----------- notebooks/15-error-mitigation.ipynb | 1390 --------------- notebooks/16-advanced-topics.ipynb | 731 -------- notebooks/16b-sdk-reference.ipynb | 847 --------- notebooks/README.md | 26 - notebooks/go.mod | 18 - notebooks/go.sum | 20 - notebooks/tools.go | 53 - textbook/app.js | 29 + textbook/chapters/01-qubits.html | 73 + textbook/chapters/02-gates.html | 108 ++ textbook/chapters/03-measurement.html | 87 + textbook/chapters/04-entanglement.html | 123 ++ textbook/chapters/05-algorithms.html | 158 ++ textbook/index.html | 28 + textbook/style.css | 28 + textbook/wasm/main.go | 78 + 36 files changed, 730 insertions(+), 21786 deletions(-) delete mode 100644 notebooks/01-the-qubit.ipynb delete mode 100644 notebooks/02-single-qubit-gates.ipynb delete mode 100644 notebooks/03-measurement.ipynb delete mode 100644 notebooks/04-multi-qubit-gates.ipynb delete mode 100644 notebooks/05-entanglement.ipynb delete mode 100644 notebooks/06-teleportation.ipynb delete mode 100644 notebooks/07-quantum-fourier-transform.ipynb delete mode 100644 notebooks/08-phase-estimation.ipynb delete mode 100644 notebooks/08b-textbook-algorithms.ipynb delete mode 100644 notebooks/09-grovers-search.ipynb delete mode 100644 notebooks/10-shors-algorithm.ipynb delete mode 100644 notebooks/10b-quantum-counting.ipynb delete mode 100644 notebooks/11-noise-and-decoherence.ipynb delete mode 100644 notebooks/11b-quantum-error-correction.ipynb delete mode 100644 notebooks/12-transpilation.ipynb delete mode 100644 notebooks/13-variational-algorithms.ipynb delete mode 100644 notebooks/14-quantum-machine-learning.ipynb delete mode 100644 notebooks/15-error-mitigation.ipynb delete mode 100644 notebooks/16-advanced-topics.ipynb delete mode 100644 notebooks/16b-sdk-reference.ipynb delete mode 100644 notebooks/README.md delete mode 100644 notebooks/go.mod delete mode 100644 notebooks/go.sum delete mode 100644 notebooks/tools.go create mode 100644 textbook/app.js create mode 100644 textbook/chapters/01-qubits.html create mode 100644 textbook/chapters/02-gates.html create mode 100644 textbook/chapters/03-measurement.html create mode 100644 textbook/chapters/04-entanglement.html create mode 100644 textbook/chapters/05-algorithms.html create mode 100644 textbook/index.html create mode 100644 textbook/style.css create mode 100644 textbook/wasm/main.go diff --git a/.gitignore b/.gitignore index 6fc2df4..36f0112 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,7 @@ go.work.sum # Claude docs/ + +# Textbook build artifacts +textbook/main.wasm +textbook/wasm_exec.js diff --git a/Makefile b/Makefile index ad1a428..ee2dbd5 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: test test-race test-all lint vet fuzz bench bench-gpu-cuda test-gpu-cuda coverage clean hooks +.PHONY: test test-race test-all lint vet fuzz bench bench-gpu-cuda test-gpu-cuda coverage clean hooks textbook textbook-serve textbook-clean test: go test -count=1 -timeout=5m ./... @@ -63,3 +63,14 @@ hooks: clean: rm -f coverage.out coverage.html go clean -testcache + +textbook: + cp "$$(go env GOROOT)/lib/wasm/wasm_exec.js" textbook/wasm_exec.js + cd textbook/wasm && GOOS=js GOARCH=wasm go build -ldflags="-w -s" -o ../main.wasm . + +textbook-serve: textbook + @echo "Serving textbook at http://localhost:8080" + python3 -m http.server 8080 -d textbook + +textbook-clean: + rm -f textbook/main.wasm textbook/wasm_exec.js diff --git a/README.md b/README.md index 9754975..dbe4e30 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Requires Go 1.24+. | Pulse Programming | OpenPulse model, waveforms, defcal | | Noise Modeling | Kraus operators, device noise models, depolarizing/amplitude-damping channels | | Observability | Zero-dep hooks + OpenTelemetry and Prometheus bridges | -| Education | 20 Jupyter notebooks from qubits to Shor's algorithm (Go kernel via [gonb](https://github.com/janpfeifer/gonb)) | +| Education | Textbook from qubits to Shor's algorithm (Go via [gonb](https://github.com/janpfeifer/gonb)) | ``` q0: ─H───@── @@ -70,7 +70,7 @@ q1: ─────X── ## Documentation - [API Reference](https://pkg.go.dev/github.com/splch/goqu) -- [Notebooks](notebooks/) +- [Textbook](textbook/) - [Benchmarks](https://splch.github.io/goqu/dev/bench/) ## Contributing diff --git a/notebooks/01-the-qubit.ipynb b/notebooks/01-the-qubit.ipynb deleted file mode 100644 index 09f8c94..0000000 --- a/notebooks/01-the-qubit.ipynb +++ /dev/null @@ -1,597 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a1b2c3d4", - "metadata": {}, - "source": [ - "# Notebook 01 — The Qubit and Superposition\n", - "\n", - "**Prerequisites:** Go programming basics and a working gonb installation (see setup below).\n", - "\n", - "Welcome to the first notebook in our 16-part quantum computing curriculum built on the **Goqu** SDK. By the end of this notebook you will be able to:\n", - "\n", - "1. Describe what a qubit is mathematically (a 2D complex vector).\n", - "2. Build and visualize quantum circuits in Go.\n", - "3. Explain superposition, probability amplitudes, and the Born rule.\n", - "4. Demonstrate, with code, that superposition is **not** a coin flip.\n", - "\n", - "---\n", - "\n", - "### Prerequisites\n", - "\n", - "You need the **gonb** Go kernel for Jupyter. Install it with:\n", - "\n", - "```bash\n", - "go install github.com/janpfeifer/gonb@latest\n", - "gonb --install\n", - "```\n", - "\n", - "Then open this notebook in JupyterLab or VS Code and select the **Go (gonb)** kernel." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "b2c3d4e5", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"fmt\"\n", - "\t\"math\"\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/sim/statevector\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "c3d4e5f6", - "metadata": {}, - "source": [ - "## What is a qubit?\n", - "\n", - "A classical bit is either 0 or 1. A **qubit** (quantum bit) is a two-dimensional complex vector:\n", - "\n", - "$$|\\psi\\rangle = \\alpha\\,|0\\rangle + \\beta\\,|1\\rangle$$\n", - "\n", - "where $\\alpha$ and $\\beta$ are **probability amplitudes** — complex numbers satisfying $|\\alpha|^2 + |\\beta|^2 = 1$.\n", - "\n", - "The **computational basis** consists of two orthogonal states:\n", - "\n", - "| State | Vector | Meaning |\n", - "|-------|--------|---------|\n", - "| $\\|0\\rangle$ | $\\begin{pmatrix}1\\\\0\\end{pmatrix}$ | \"off\", ground state |\n", - "| $\\|1\\rangle$ | $\\begin{pmatrix}0\\\\1\\end{pmatrix}$ | \"on\", excited state |\n", - "\n", - "When we **measure** a qubit, we get outcome 0 with probability $|\\alpha|^2$ and outcome 1 with probability $|\\beta|^2$. This is the **Born rule**.\n", - "\n", - "The Hadamard gate **H** transforms $|0\\rangle$ into the **equal superposition** state:\n", - "\n", - "$$H|0\\rangle = \\frac{1}{\\sqrt{2}}|0\\rangle + \\frac{1}{\\sqrt{2}}|1\\rangle \\;=\\; |+\\rangle$$\n", - "\n", - "In this state, each outcome has probability $(1/\\sqrt{2})^2 = 1/2$.\n", - "\n", - "### Qubit ordering convention\n", - "\n", - "Throughout this textbook, bitstrings are displayed in **MSB-first** (most-significant-bit first) order. For an $n$-qubit system, the state $|q_{n-1}\\,\\ldots\\,q_1\\,q_0\\rangle$ has the **highest-index qubit on the left** and **qubit 0 on the right**. For example, in a 2-qubit system:\n", - "\n", - "| Bitstring | Qubit 1 (left) | Qubit 0 (right) |\n", - "|:---------:|:--------------:|:---------------:|\n", - "| \\|00⟩ | 0 | 0 |\n", - "| \\|01⟩ | 0 | 1 |\n", - "| \\|10⟩ | 1 | 0 |\n", - "| \\|11⟩ | 1 | 1 |\n", - "\n", - "This is the same convention used by Qiskit, Cirq, and most quantum computing frameworks. Circuit diagrams draw qubit 0 as the **top wire**." - ] - }, - { - "cell_type": "markdown", - "id": "d4e5f6a7", - "metadata": {}, - "source": [ - "## Your first circuit\n", - "\n", - "Let's build a single-qubit circuit that applies the Hadamard gate, then print its text diagram.\n", - "\n", - "The Goqu builder uses a **fluent API**:\n", - "- `builder.New(name, nQubits)` creates a new circuit builder.\n", - "- `.H(0)` applies Hadamard to qubit 0.\n", - "- `.Build()` finalizes the circuit.\n", - "- `draw.String(circuit)` renders an ASCII diagram." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e5f6a7b8", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "H\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "c, err := builder.New(\"hadamard\", 1).H(0).Build()\n", - "if err != nil {\n", - "\tfmt.Println(\"Error:\", err)\n", - "} else {\n", - "\tgonbui.DisplayHTML(draw.SVG(c))\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "f6a7b8c9", - "metadata": {}, - "source": [ - "## Inspecting the statevector\n", - "\n", - "A **statevector simulator** tracks the exact amplitudes of every basis state. There is no randomness until we measure.\n", - "\n", - "After applying H to $|0\\rangle$, we expect the statevector to be:\n", - "\n", - "$$\\left[\\frac{1}{\\sqrt{2}},\\; \\frac{1}{\\sqrt{2}}\\right] \\approx [0.707,\\; 0.707]$$\n", - "\n", - "Let's verify with `statevector.New(1)` (one qubit, initialized to $|0\\rangle$)." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "a7b8c9d0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Statevector: [(0.7071067811865476+0i) (0.7071067811865476+0i)]\n", - "|0> amplitude: 0.707107 probability: 0.5000\n", - "|1> amplitude: 0.707107 probability: 0.5000\n" - ] - } - ], - "source": [ - "%%\n", - "sim := statevector.New(1)\n", - "c, _ := builder.New(\"h\", 1).H(0).Build()\n", - "sim.Evolve(c)\n", - "sv := sim.StateVector()\n", - "fmt.Printf(\"Statevector: %v\\n\", sv)\n", - "fmt.Printf(\"|0> amplitude: %.6f probability: %.4f\\n\", real(sv[0]), real(sv[0])*real(sv[0]))\n", - "fmt.Printf(\"|1> amplitude: %.6f probability: %.4f\\n\", real(sv[1]), real(sv[1])*real(sv[1]))" - ] - }, - { - "cell_type": "markdown", - "id": "b8c9d0e1", - "metadata": {}, - "source": [ - "## Visualizing on the Bloch sphere\n", - "\n", - "Every single-qubit pure state can be represented as a point on the **Bloch sphere**:\n", - "\n", - "- $|0\\rangle$ is the **north pole**.\n", - "- $|1\\rangle$ is the **south pole**.\n", - "- $|+\\rangle$ sits on the **equator** (pointing along the +X axis).\n", - "\n", - "The `viz.Bloch` function takes a 2-element statevector and returns an SVG rendering of the sphere." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "c9d0e1f2", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "|+> state on the Bloch sphere\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "|+⟩\n", - "|-⟩\n", - "|+i⟩\n", - "|-i⟩\n", - "|0⟩\n", - "|1⟩\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "sim := statevector.New(1)\n", - "c, _ := builder.New(\"bloch-plus\", 1).H(0).Build()\n", - "sim.Evolve(c)\n", - "svg := viz.Bloch(sim.StateVector(), viz.WithTitle(\"|+> state on the Bloch sphere\"))\n", - "gonbui.DisplayHTML(svg)" - ] - }, - { - "cell_type": "markdown", - "id": "d0e1f2a3", - "metadata": {}, - "source": [ - "## Measurement and the Born rule\n", - "\n", - "When we add `.MeasureAll()` to the circuit and run it for many **shots**, each shot collapses the superposition into a definite outcome. The fraction of shots yielding each outcome converges to the Born-rule probabilities.\n", - "\n", - "For $|+\\rangle$, we expect roughly 50% \"0\" and 50% \"1\"." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "e1f2a3b4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Counts: map[0:509 1:491]\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "H|0> — 1000 shots\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "100\n", - "\n", - "200\n", - "\n", - "300\n", - "\n", - "400\n", - "\n", - "500\n", - "\n", - "600\n", - "\n", - "0\n", - "\n", - "1\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "c, _ := builder.New(\"measure-plus\", 1).H(0).MeasureAll().Build()\n", - "sim := statevector.New(1)\n", - "counts, _ := sim.Run(c, 1000)\n", - "fmt.Println(\"Counts:\", counts)\n", - "gonbui.DisplayHTML(viz.Histogram(counts, viz.WithTitle(\"H|0> — 1000 shots\")))" - ] - }, - { - "cell_type": "markdown", - "id": "f2a3b4c5", - "metadata": {}, - "source": [ - "## Predict, then verify\n", - "\n", - "Before running the next cell, **pause and predict**: what will the measurement outcomes be for a circuit that applies only an **X gate** (the quantum NOT gate) to $|0\\rangle$?\n", - "\n", - "The X gate flips a qubit:\n", - "\n", - "$$X|0\\rangle = |1\\rangle, \\qquad X|1\\rangle = |0\\rangle$$\n", - "\n", - "So we should **always** get outcome \"1\" — 100% of the time." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "a3b4c5d6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "X\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Counts: map[1:1000]\n" - ] - } - ], - "source": [ - "%%\n", - "c, _ := builder.New(\"x-gate\", 1).X(0).MeasureAll().Build()\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "sim := statevector.New(1)\n", - "counts, _ := sim.Run(c, 1000)\n", - "fmt.Println(\"Counts:\", counts)" - ] - }, - { - "cell_type": "markdown", - "id": "b4c5d6e7", - "metadata": {}, - "source": [ - "## Superposition is NOT a coin flip\n", - "\n", - "A common misconception is that a qubit in superposition is \"just a coin flip — we don't know the answer yet, but it's already decided.\" This is wrong. A qubit in superposition carries **phase information** that can produce **interference**, something no classical coin can do.\n", - "\n", - "### The proof: H followed by H\n", - "\n", - "If superposition were merely classical ignorance (a coin flip), then:\n", - "- H would create a 50/50 coin.\n", - "- A second H would flip the coin again — still 50/50.\n", - "\n", - "But quantum mechanics says:\n", - "\n", - "$$H \\cdot H |0\\rangle = |0\\rangle$$\n", - "\n", - "The two Hadamard gates produce **constructive interference** for $|0\\rangle$ and **destructive interference** for $|1\\rangle$, perfectly canceling the $|1\\rangle$ amplitude. The result is deterministically $|0\\rangle$ — 100% of the time.\n", - "\n", - "Mathematically:\n", - "\n", - "$$H|+\\rangle = H\\left(\\frac{|0\\rangle + |1\\rangle}{\\sqrt{2}}\\right) = \\frac{H|0\\rangle + H|1\\rangle}{\\sqrt{2}} = \\frac{(|0\\rangle + |1\\rangle) + (|0\\rangle - |1\\rangle)}{2} = |0\\rangle$$\n", - "\n", - "Let's verify this and contrast it with what a \"coin flip\" model would predict." - ] - }, - { - "cell_type": "markdown", - "id": "ik28cnozf1c", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. True or false: A qubit in superposition is in state |0⟩ AND state |1⟩ at the same time. Why or why not?\n", - "2. Why does H·H|0⟩ = |0⟩, but a coin flipped twice still gives a 50/50 outcome?\n", - "3. If a qubit is in state |ψ⟩ = √(0.3)|0⟩ + √(0.7)|1⟩, what is the probability of measuring |0⟩?" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "c5d6e7f8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Circuit: H -> H\n", - "Statevector after H->H: [(1.0000000000000002+0i) (0+0i)]\n", - "\n", - "Quantum result (H->H, 1000 shots):\n", - "map[0:1000]\n", - "\n", - "If superposition were a coin flip, we'd expect ~500 '0' and ~500 '1'.\n", - "Instead we get 1000 '0' and 0 '1' — proof of quantum interference!\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "H\n", - "H\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Quantum: H -> H should give |0> with 100% probability (interference!)\n", - "cHH, _ := builder.New(\"H-H interference\", 1).H(0).H(0).MeasureAll().Build()\n", - "fmt.Println(\"Circuit: H -> H\")\n", - "gonbui.DisplayHTML(draw.SVG(cHH))\n", - "\n", - "sim := statevector.New(1)\n", - "\n", - "// Check the statevector before measurement\n", - "cHHnoMeasure, _ := builder.New(\"H-H sv\", 1).H(0).H(0).Build()\n", - "sim.Evolve(cHHnoMeasure)\n", - "fmt.Printf(\"Statevector after H->H: %v\\n\\n\", sim.StateVector())\n", - "\n", - "// Run with measurement\n", - "counts, _ := sim.Run(cHH, 1000)\n", - "fmt.Println(\"Quantum result (H->H, 1000 shots):\")\n", - "fmt.Println(counts)\n", - "fmt.Println()\n", - "fmt.Println(\"If superposition were a coin flip, we'd expect ~500 '0' and ~500 '1'.\")\n", - "fmt.Println(\"Instead we get 1000 '0' and 0 '1' — proof of quantum interference!\")" - ] - }, - { - "cell_type": "markdown", - "id": "d6e7f8a9", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## Exercises\n", - "\n", - "Try these on your own before peeking at the hints.\n", - "\n", - "### Exercise 1 — Deterministic |1>\n", - "\n", - "Build a circuit that **always** measures `1`. Run it for 1000 shots and display the histogram.\n", - "\n", - "*Hint: Which single gate flips $|0\\rangle$ to $|1\\rangle$?*" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "e7f8a9b0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the gate!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: Build a circuit that always measures |1>\n", - "// Expected: all 1000 shots should give \"1\"\n", - "//\n", - "// Replace the ... with the correct gate call\n", - "// c, _ := builder.New(\"always-one\", 1). ... .MeasureAll().Build()\n", - "// sim := statevector.New(1)\n", - "// counts, _ := sim.Run(c, 1000)\n", - "// gonbui.DisplayHTML(viz.Histogram(counts, viz.WithTitle(\"Exercise 1: Always |1>\")))\n", - "fmt.Println(\"Uncomment the code above and fill in the gate!\")" - ] - }, - { - "cell_type": "markdown", - "id": "f8a9b0c1", - "metadata": {}, - "source": [ - "### Exercise 2 — A 75/25 split\n", - "\n", - "Create a circuit that measures `0` with 75% probability and `1` with 25% probability. Run it for 1000 shots and display the histogram.\n", - "\n", - "*Hint: The RY gate rotates the qubit on the Bloch sphere. If you want $P(|0\\rangle) = \\cos^2(\\theta/2) = 0.75$, solve for $\\theta$. The `math.Acos` and `math.Sqrt` functions will help.*" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "a9b0c1d2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the probability!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: Build a circuit that gives 75% |0> and 25% |1>\n", - "// Expected: approximately 750 \"0\" and 250 \"1\" (±50)\n", - "//\n", - "// theta := 2 * math.Acos(math.Sqrt( ... ))\n", - "// c, _ := builder.New(\"75-25\", 1).RY(theta, 0).MeasureAll().Build()\n", - "// sim := statevector.New(1)\n", - "// counts, _ := sim.Run(c, 1000)\n", - "// gonbui.DisplayHTML(viz.Histogram(counts, viz.WithTitle(\"Exercise 2: 75/25 split\")))\n", - "_ = math.Sqrt // suppress unused import\n", - "fmt.Println(\"Uncomment the code above and fill in the probability!\")" - ] - }, - { - "cell_type": "markdown", - "id": "b0c1d2e3", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## Key takeaways\n", - "\n", - "1. **A qubit** is a unit vector in a 2D complex vector space, written $|\\psi\\rangle = \\alpha|0\\rangle + \\beta|1\\rangle$.\n", - "\n", - "2. **Superposition** means $\\alpha$ and $\\beta$ can both be non-zero simultaneously. The qubit is in a single, well-defined quantum state -- but that state encodes probabilities for multiple measurement outcomes.\n", - "\n", - "3. **The Born rule** says measurement outcome probabilities are the squared magnitudes of the amplitudes: $P(0) = |\\alpha|^2$, $P(1) = |\\beta|^2$.\n", - "\n", - "4. **Interference** is the hallmark of quantum mechanics. Amplitudes can add constructively or destructively, as we saw with $H \\cdot H|0\\rangle = |0\\rangle$. No classical probability model can reproduce this.\n", - "\n", - "5. **The Bloch sphere** is a geometric way to visualize single-qubit states, with $|0\\rangle$ at the north pole, $|1\\rangle$ at the south pole, and superposition states on the surface.\n", - "\n", - "---\n", - "\n", - "**Next up:** Notebook 02 -- Single-Qubit Gates, where we'll explore all the ways to manipulate a single qubit." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/02-single-qubit-gates.ipynb b/notebooks/02-single-qubit-gates.ipynb deleted file mode 100644 index f9e7b14..0000000 --- a/notebooks/02-single-qubit-gates.ipynb +++ /dev/null @@ -1,921 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 02 - Single-Qubit Gates\n", - "\n", - "**Prerequisites:** Notebook 01. Familiarity with qubits, superposition, and the Bloch sphere.\n", - "\n", - "Every quantum computation is built from **gates** -- unitary transformations that rotate the state of a qubit on the Bloch sphere. In this notebook we explore the standard single-qubit gates available in Goqu:\n", - "\n", - "- **Pauli gates** (X, Y, Z) -- 180-degree rotations around the three Bloch sphere axes\n", - "- **Hadamard** (H) -- creates equal superposition\n", - "- **Phase gates** (S, T) -- fractional Z-rotations forming the S^2 = Z, T^2 = S, T^4 = Z chain\n", - "- **Rotation gates** (RX, RY, RZ) -- continuous rotations, universal for single-qubit operations\n", - "- **Gate inverses** and **gate powers**\n", - "\n", - "Geometrically, every single-qubit gate is a rotation of the Bloch sphere. A gate's 2x2 unitary matrix encodes the axis and angle of that rotation. We will visualize these rotations using Goqu's Bloch sphere renderer.\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Describe** the Pauli, phase, and rotation gates and their matrix representations.\n", - "2. **Implement** any single-qubit rotation using RX, RY, RZ gates.\n", - "3. **Verify** the S²=Z, T²=S, T⁴=Z hierarchy using gate power operations.\n", - "4. **Explain** why any single-qubit unitary can be decomposed into three rotations." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"fmt\"\n", - "\t\"math\"\n", - "\t\"math/cmplx\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/circuit/gate\"\n", - "\t\"github.com/splch/goqu/sim/statevector\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Pauli Gates: X, Y, Z\n", - "\n", - "The three Pauli gates are 180-degree rotations around the X, Y, and Z axes of the Bloch sphere:\n", - "\n", - "| Gate | Action | Matrix |\n", - "|------|--------|--------|\n", - "| **X** (bit-flip) | \\|0> -> \\|1>, \\|1> -> \\|0> | `[[0,1],[1,0]]` |\n", - "| **Y** | \\|0> -> i\\|1>, \\|1> -> -i\\|0> | `[[0,-i],[i,0]]` |\n", - "| **Z** (phase-flip) | \\|0> -> \\|0>, \\|1> -> -\\|1> | `[[1,0],[0,-1]]` |\n", - "\n", - "All three are **self-inverse**: applying the same gate twice returns the qubit to its original state." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "X|0> = [(0+0i) (1+0i)]\n", - "Circuit:\n", - "X matrix: [[(0+0i), (1+0i)], [(1+0i), (0+0i)]]\n", - "Y matrix: [[(0+0i), (0-1i)], [(0+1i), (0+0i)]]\n", - "Z matrix: [[(1+0i), (0+0i)], [(0+0i), (-1+0i)]]\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "X\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Apply X gate to |0> -- should produce |1>\n", - "c, _ := builder.New(\"X-gate\", 1).X(0).Build()\n", - "sim := statevector.New(1)\n", - "sim.Evolve(c)\n", - "sv := sim.StateVector()\n", - "fmt.Println(\"X|0> =\", sv)\n", - "fmt.Println(\"Circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "\n", - "// Print the Pauli gate matrices\n", - "for _, info := range []struct {\n", - "\tname string\n", - "\tg gate.Gate\n", - "}{\n", - "\t{\"X\", gate.X},\n", - "\t{\"Y\", gate.Y},\n", - "\t{\"Z\", gate.Z},\n", - "} {\n", - "\tm := info.g.Matrix()\n", - "\tfmt.Printf(\"%s matrix: [[%v, %v], [%v, %v]]\\n\", info.name, m[0], m[1], m[2], m[3])\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## The Hadamard Gate\n", - "\n", - "The Hadamard gate **H** is arguably the most important single-qubit gate. It maps computational basis states to superposition states:\n", - "\n", - "- H|0> = |+> = (|0> + |1>) / sqrt(2)\n", - "- H|1> = |-> = (|0> - |1>) / sqrt(2)\n", - "\n", - "On the Bloch sphere, H is a 180-degree rotation around the axis halfway between X and Z. Like the Pauli gates, H is self-inverse: H^2 = I." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "H|0> = [(0.7071067811865476+0i) (0.7071067811865476+0i)]\n", - " |0> amplitude: (0.7071+0.0000i)\n", - " |1> amplitude: (0.7071+0.0000i)\n", - " Both equal 1/sqrt(2) = 0.7071\n", - "\n", - "Circuit:\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "H\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "|+> = H|0>\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "|+⟩\n", - "|-⟩\n", - "|+i⟩\n", - "|-i⟩\n", - "|0⟩\n", - "|1⟩\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Apply H to |0> and visualize on the Bloch sphere\n", - "c, _ := builder.New(\"H-gate\", 1).H(0).Build()\n", - "sim := statevector.New(1)\n", - "sim.Evolve(c)\n", - "sv := sim.StateVector()\n", - "fmt.Println(\"H|0> =\", sv)\n", - "fmt.Printf(\" |0> amplitude: %.4f\\n\", sv[0])\n", - "fmt.Printf(\" |1> amplitude: %.4f\\n\", sv[1])\n", - "fmt.Printf(\" Both equal 1/sqrt(2) = %.4f\\n\", 1/math.Sqrt(2))\n", - "fmt.Println()\n", - "fmt.Println(\"Circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "\n", - "// Render on the Bloch sphere -- |+> points along the +X axis\n", - "gonbui.DisplayHTML(viz.Bloch(sv, viz.WithTitle(\"|+> = H|0>\")))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Phase Gates: S and T\n", - "\n", - "Phase gates rotate the qubit around the Z axis by fractional angles:\n", - "\n", - "| Gate | Phase on |1> | Relation |\n", - "|------|------------|----------|\n", - "| **Z** | e^(i*pi) = -1 | pi rotation |\n", - "| **S** | e^(i*pi/2) = i | S^2 = Z |\n", - "| **T** | e^(i*pi/4) | T^2 = S, T^4 = Z |\n", - "\n", - "The T gate is critical for universal quantum computing: the **Clifford+T** gate set can approximate any unitary to arbitrary precision (Solovay-Kitaev theorem).\n", - "\n", - "Let's verify the S^2 = Z and T^2 = S chain by comparing gate matrices." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "S^2 matrix: [(1+0i) (0+0i) (0+0i) (-1+0i)]\n", - "Z matrix: [(1+0i) (0+0i) (0+0i) (-1+0i)]\n", - "S^2 == Z? true\n", - "\n", - "T^2 matrix: [(1+0i) (0+0i) (0+0i) (4.266421588589642e-17+1.0000000000000002i)]\n", - "S matrix: [(1+0i) (0+0i) (0+0i) (0+1i)]\n", - "T^2 == S? true\n", - "\n", - "T^4 matrix: [(1+0i) (0+0i) (0+0i) (-1.0000000000000004+1.143450299824811e-16i)]\n", - "Z matrix: [(1+0i) (0+0i) (0+0i) (-1+0i)]\n", - "T^4 == Z? true\n" - ] - } - ], - "source": [ - "%%\n", - "// Helper to compare two gate matrices\n", - "matricesEqual := func(a, b []complex128, tol float64) bool {\n", - "\tif len(a) != len(b) {\n", - "\t\treturn false\n", - "\t}\n", - "\tfor i := range a {\n", - "\t\tif cmplx.Abs(a[i]-b[i]) > tol {\n", - "\t\t\treturn false\n", - "\t\t}\n", - "\t}\n", - "\treturn true\n", - "}\n", - "\n", - "// S^2 = Z\n", - "s2 := gate.Pow(gate.S, 2)\n", - "fmt.Println(\"S^2 matrix:\", s2.Matrix())\n", - "fmt.Println(\"Z matrix:\", gate.Z.Matrix())\n", - "fmt.Println(\"S^2 == Z?\", matricesEqual(s2.Matrix(), gate.Z.Matrix(), 1e-10))\n", - "fmt.Println()\n", - "\n", - "// T^2 = S\n", - "t2 := gate.Pow(gate.T, 2)\n", - "fmt.Println(\"T^2 matrix:\", t2.Matrix())\n", - "fmt.Println(\"S matrix:\", gate.S.Matrix())\n", - "fmt.Println(\"T^2 == S?\", matricesEqual(t2.Matrix(), gate.S.Matrix(), 1e-10))\n", - "fmt.Println()\n", - "\n", - "// T^4 = Z\n", - "t4 := gate.Pow(gate.T, 4)\n", - "fmt.Println(\"T^4 matrix:\", t4.Matrix())\n", - "fmt.Println(\"Z matrix:\", gate.Z.Matrix())\n", - "fmt.Println(\"T^4 == Z?\", matricesEqual(t4.Matrix(), gate.Z.Matrix(), 1e-10))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Rotation Gates: RX, RY, RZ\n", - "\n", - "Rotation gates provide **continuous** rotation around the Bloch sphere axes:\n", - "\n", - "- **RX(theta)** = exp(-i * theta/2 * X) -- rotation around the X axis\n", - "- **RY(theta)** = exp(-i * theta/2 * Y) -- rotation around the Y axis \n", - "- **RZ(theta)** = exp(-i * theta/2 * Z) -- rotation around the Z axis\n", - "\n", - "These gates are **universal**: any single-qubit unitary can be decomposed as a sequence of rotations RZ(a) * RY(b) * RZ(c) (the ZYZ Euler decomposition).\n", - "\n", - "Let's sweep RX from 0 to 2*pi and observe how the statevector evolves." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "RX(theta)|0> at several angles:\n", - "------------------------------------------\n", - " RX( 0)|0> = [( 1.0000 +0.0000i), ( 0.0000 +0.0000i)] P(|0>)=1.000 P(|1>)=0.000\n", - " RX( pi/4)|0> = [( 0.9239 +0.0000i), ( 0.0000 -0.3827i)] P(|0>)=0.854 P(|1>)=0.146\n", - " RX( pi/2)|0> = [( 0.7071 +0.0000i), ( 0.0000 -0.7071i)] P(|0>)=0.500 P(|1>)=0.500\n", - " RX( pi)|0> = [( 0.0000 +0.0000i), ( 0.0000 -1.0000i)] P(|0>)=0.000 P(|1>)=1.000\n", - " RX( 3pi/2)|0> = [( -0.7071 +0.0000i), ( 0.0000 -0.7071i)] P(|0>)=0.500 P(|1>)=0.500\n", - " RX( 2pi)|0> = [( -1.0000 +0.0000i), ( 0.0000 -0.0000i)] P(|0>)=1.000 P(|1>)=0.000\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "RX(pi/2)|0>\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "|+⟩\n", - "|-⟩\n", - "|+i⟩\n", - "|-i⟩\n", - "|0⟩\n", - "|1⟩\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// RX(theta) sweep from 0 to 2*pi\n", - "fmt.Println(\"RX(theta)|0> at several angles:\")\n", - "fmt.Println(\"------------------------------------------\")\n", - "angles := []struct {\n", - "\tname string\n", - "\ttheta float64\n", - "}{\n", - "\t{\"0\", 0},\n", - "\t{\"pi/4\", math.Pi / 4},\n", - "\t{\"pi/2\", math.Pi / 2},\n", - "\t{\"pi\", math.Pi},\n", - "\t{\"3pi/2\", 3 * math.Pi / 2},\n", - "\t{\"2pi\", 2 * math.Pi},\n", - "}\n", - "\n", - "for _, a := range angles {\n", - "\tc, _ := builder.New(\"rx\", 1).RX(a.theta, 0).Build()\n", - "\tsim := statevector.New(1)\n", - "\tsim.Evolve(c)\n", - "\tsv := sim.StateVector()\n", - "\tp0 := real(sv[0])*real(sv[0]) + imag(sv[0])*imag(sv[0])\n", - "\tp1 := real(sv[1])*real(sv[1]) + imag(sv[1])*imag(sv[1])\n", - "\tfmt.Printf(\" RX(%6s)|0> = [%8.4f, %8.4f] P(|0>)=%.3f P(|1>)=%.3f\\n\",\n", - "\t\ta.name, sv[0], sv[1], p0, p1)\n", - "}\n", - "\n", - "// Visualize RX(pi/2)|0> on the Bloch sphere\n", - "c, _ := builder.New(\"rx-pi/2\", 1).RX(math.Pi/2, 0).Build()\n", - "sim := statevector.New(1)\n", - "sim.Evolve(c)\n", - "gonbui.DisplayHTML(viz.Bloch(sim.StateVector(), viz.WithTitle(\"RX(pi/2)|0>\")))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Gate Inverses\n", - "\n", - "Every quantum gate is unitary, which means it has an inverse (its conjugate transpose, or **adjoint**). Applying a gate followed by its inverse returns the qubit to its original state.\n", - "\n", - "In Goqu, call `.Inverse()` on any gate to get its adjoint:\n", - "- `gate.H.Inverse()` returns H (since H is self-inverse)\n", - "- `gate.S.Inverse()` returns S-dagger (Sdg)\n", - "- `gate.T.Inverse()` returns T-dagger (Tdg)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Gate -> Inverse -> back to |0>:\n", - "-------------------------------\n", - " H then H^dag : P(|0>) = 1.000000\n", - " S then S^dag : P(|0>) = 1.000000\n", - " T then T^dag : P(|0>) = 1.000000\n", - " X then X^dag : P(|0>) = 1.000000\n", - " RX(pi/3) then RX(pi/3)^dag : P(|0>) = 1.000000\n", - "\n", - "S.Inverse() name: S†\n", - "T.Inverse() name: T†\n" - ] - } - ], - "source": [ - "%%\n", - "// Apply a gate then its inverse -- should return to |0>\n", - "fmt.Println(\"Gate -> Inverse -> back to |0>:\")\n", - "fmt.Println(\"-------------------------------\")\n", - "\n", - "for _, info := range []struct {\n", - "\tname string\n", - "\tg gate.Gate\n", - "}{\n", - "\t{\"H\", gate.H},\n", - "\t{\"S\", gate.S},\n", - "\t{\"T\", gate.T},\n", - "\t{\"X\", gate.X},\n", - "\t{\"RX(pi/3)\", gate.RX(math.Pi / 3)},\n", - "} {\n", - "\tinv := info.g.Inverse()\n", - "\tc, _ := builder.New(\"inv\", 1).\n", - "\t\tApply(info.g, 0).\n", - "\t\tApply(inv, 0).\n", - "\t\tBuild()\n", - "\tsim := statevector.New(1)\n", - "\tsim.Evolve(c)\n", - "\tsv := sim.StateVector()\n", - "\tp0 := real(sv[0])*real(sv[0]) + imag(sv[0])*imag(sv[0])\n", - "\tfmt.Printf(\" %10s then %s^dag : P(|0>) = %.6f\\n\", info.name, info.name, p0)\n", - "}\n", - "\n", - "// Show that S.Inverse() == Sdg\n", - "fmt.Println()\n", - "fmt.Println(\"S.Inverse() name:\", gate.S.Inverse().Name())\n", - "fmt.Println(\"T.Inverse() name:\", gate.T.Inverse().Name())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Global Phase\n", - "\n", - "Two quantum states that differ only by a **global phase** (a multiplicative factor $e^{i\\phi}$) are physically indistinguishable -- they produce identical measurement probabilities and identical expectation values. For example, $|0\\rangle$ and $e^{i\\pi/4}|0\\rangle$ are the same physical state.\n", - "\n", - "This means gate decompositions may differ by a global phase and still be physically equivalent. When we say \"RY(pi/2) followed by RZ(pi) is equivalent to H,\" we mean they produce the same measurement statistics, even though their matrices may differ by a factor of $e^{i\\phi}$.\n", - "\n", - "Global phase is different from **relative phase** (the phase difference between amplitudes), which IS physically observable through interference." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** What state does $\\text{RY}(\\pi/2)|0\\rangle$ produce? Where does it sit on the Bloch sphere?\n", - "\n", - "**Pause and predict** before running the next cell.\n", - "\n", - "*Hint: Think about what a 90-degree rotation around the Y-axis does to the north pole of the Bloch sphere.*" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "RY(pi/2)|0> = [(0.7071067811865476+0i) (0.7071067811865475+0i)]\n", - "H|0> = [(0.7071067811865476+0i) (0.7071067811865476+0i)]\n", - "\n", - "Both produce equal superposition with P(|0>) = P(|1>) = 0.5\n", - "RY version: P(|0>)=0.5000, P(|1>)=0.5000\n", - "H version: P(|0>)=0.5000, P(|1>)=0.5000\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "RY(pi/2)|0>\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "|+⟩\n", - "|-⟩\n", - "|+i⟩\n", - "|-i⟩\n", - "|0⟩\n", - "|1⟩\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "H|0>\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "|+⟩\n", - "|-⟩\n", - "|+i⟩\n", - "|-i⟩\n", - "|0⟩\n", - "|1⟩\n", - "\n", - "\n", - "\n", - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// RY(pi/2)|0> should produce |+> (same as H|0>)\n", - "cRY, _ := builder.New(\"ry\", 1).RY(math.Pi/2, 0).Build()\n", - "cH, _ := builder.New(\"h\", 1).H(0).Build()\n", - "\n", - "simRY := statevector.New(1)\n", - "simRY.Evolve(cRY)\n", - "svRY := simRY.StateVector()\n", - "\n", - "simH := statevector.New(1)\n", - "simH.Evolve(cH)\n", - "svH := simH.StateVector()\n", - "\n", - "fmt.Println(\"RY(pi/2)|0> =\", svRY)\n", - "fmt.Println(\"H|0> =\", svH)\n", - "\n", - "// They both have equal |0> and |1> amplitudes (both real, both 1/sqrt(2))\n", - "fmt.Printf(\"\\nBoth produce equal superposition with P(|0>) = P(|1>) = 0.5\\n\")\n", - "fmt.Printf(\"RY version: P(|0>)=%.4f, P(|1>)=%.4f\\n\",\n", - "\treal(svRY[0])*real(svRY[0])+imag(svRY[0])*imag(svRY[0]),\n", - "\treal(svRY[1])*real(svRY[1])+imag(svRY[1])*imag(svRY[1]))\n", - "fmt.Printf(\"H version: P(|0>)=%.4f, P(|1>)=%.4f\\n\",\n", - "\treal(svH[0])*real(svH[0])+imag(svH[0])*imag(svH[0]),\n", - "\treal(svH[1])*real(svH[1])+imag(svH[1])*imag(svH[1]))\n", - "\n", - "// Visualize both side by side\n", - "gonbui.DisplayHTML(\n", - "\t\"
\" +\n", - "\t\tviz.Bloch(svRY, viz.WithTitle(\"RY(pi/2)|0>\"), viz.WithSize(300, 300)) +\n", - "\t\tviz.Bloch(svH, viz.WithTitle(\"H|0>\"), viz.WithSize(300, 300)) +\n", - "\t\t\"
\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Power of Gates\n", - "\n", - "Goqu provides `gate.Pow(g, k)` to raise a gate to an integer power by repeated matrix multiplication. This lets us verify algebraic identities:\n", - "\n", - "- T^2 = S\n", - "- S^2 = Z\n", - "- T^4 = Z\n", - "- X^2 = I (self-inverse)\n", - "- H^2 = I (self-inverse)" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "gate.Pow(T, 2) == S? true\n", - "gate.Pow(S, 2) == Z? true\n", - "gate.Pow(T, 4) == Z? true\n", - "gate.Pow(X, 2) == I? true\n", - "gate.Pow(H, 2) == I? true\n" - ] - } - ], - "source": [ - "%%\n", - "// Helper to compare matrices\n", - "matEq := func(a, b []complex128) bool {\n", - "\tfor i := range a {\n", - "\t\tif cmplx.Abs(a[i]-b[i]) > 1e-10 {\n", - "\t\t\treturn false\n", - "\t\t}\n", - "\t}\n", - "\treturn true\n", - "}\n", - "\n", - "// gate.Pow(T, 2) == S\n", - "fmt.Println(\"gate.Pow(T, 2) == S?\", matEq(gate.Pow(gate.T, 2).Matrix(), gate.S.Matrix()))\n", - "\n", - "// gate.Pow(S, 2) == Z\n", - "fmt.Println(\"gate.Pow(S, 2) == Z?\", matEq(gate.Pow(gate.S, 2).Matrix(), gate.Z.Matrix()))\n", - "\n", - "// gate.Pow(T, 4) == Z\n", - "fmt.Println(\"gate.Pow(T, 4) == Z?\", matEq(gate.Pow(gate.T, 4).Matrix(), gate.Z.Matrix()))\n", - "\n", - "// gate.Pow(X, 2) == I\n", - "fmt.Println(\"gate.Pow(X, 2) == I?\", matEq(gate.Pow(gate.X, 2).Matrix(), gate.I.Matrix()))\n", - "\n", - "// gate.Pow(H, 2) == I\n", - "fmt.Println(\"gate.Pow(H, 2) == I?\", matEq(gate.Pow(gate.H, 2).Matrix(), gate.I.Matrix()))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. What single gate is equivalent to applying T four times?\n", - "2. Can every single-qubit gate be decomposed into RY and RZ rotations? Why?\n", - "3. On the Bloch sphere, what axis and angle does the Z gate rotate around?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "%%\n", - "// Exercise 1: Build H from RY and RZ\n", - "// Decomposition (up to global phase): H = RZ(pi) * RY(pi/2)\n", - "// In circuit order: first RY, then RZ\n", - "// Expected: P(|0>) = 0.5 and P(|1>) = 0.5, matching H|0>\n", - "//\n", - "// TODO: Fill in the rotation angles to reproduce H|0>\n", - "// cDecomp, _ := builder.New(\"h-decomp\", 1).\n", - "// \tRY( /* angle */ , 0).\n", - "// \tRZ( /* angle */ , 0).\n", - "// \tBuild()\n", - "//\n", - "// cH, _ := builder.New(\"h-direct\", 1).H(0).Build()\n", - "//\n", - "// simD := statevector.New(1)\n", - "// simD.Evolve(cDecomp)\n", - "// svD := simD.StateVector()\n", - "//\n", - "// simH := statevector.New(1)\n", - "// simH.Evolve(cH)\n", - "// svH := simH.StateVector()\n", - "//\n", - "// fmt.Println(\"RZ*RY|0> =\", svD)\n", - "// fmt.Println(\"H|0> =\", svH)\n", - "//\n", - "// // Check measurement probabilities match\n", - "// p0D := real(svD[0])*real(svD[0]) + imag(svD[0])*imag(svD[0])\n", - "// p1D := real(svD[1])*real(svD[1]) + imag(svD[1])*imag(svD[1])\n", - "// p0H := real(svH[0])*real(svH[0]) + imag(svH[0])*imag(svH[0])\n", - "// p1H := real(svH[1])*real(svH[1]) + imag(svH[1])*imag(svH[1])\n", - "// fmt.Printf(\"\\nDecomposed: P(|0>)=%.4f, P(|1>)=%.4f\\n\", p0D, p1D)\n", - "// fmt.Printf(\"Hadamard: P(|0>)=%.4f, P(|1>)=%.4f\\n\", p0H, p1H)\n", - "// fmt.Println(\"Probabilities match:\", math.Abs(p0D-p0H) < 1e-10 && math.Abs(p1D-p1H) < 1e-10)\n", - "// fmt.Println(\"\\nDecomposed circuit:\")\n", - "// fmt.Println(draw.String(cDecomp))\n", - "fmt.Println(\"Uncomment the code above and fill in the rotation angles!\")" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the rotation angles!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: Build H from RY and RZ\n", - "// Decomposition (up to global phase): H = RZ(pi) * RY(pi/2)\n", - "// In circuit order: first RY, then RZ\n", - "//\n", - "// TODO: Fill in the rotation angles to reproduce H|0>\n", - "// cDecomp, _ := builder.New(\"h-decomp\", 1).\n", - "// \tRY( /* angle */ , 0).\n", - "// \tRZ( /* angle */ , 0).\n", - "// \tBuild()\n", - "//\n", - "// cH, _ := builder.New(\"h-direct\", 1).H(0).Build()\n", - "//\n", - "// simD := statevector.New(1)\n", - "// simD.Evolve(cDecomp)\n", - "// svD := simD.StateVector()\n", - "//\n", - "// simH := statevector.New(1)\n", - "// simH.Evolve(cH)\n", - "// svH := simH.StateVector()\n", - "//\n", - "// fmt.Println(\"RZ*RY|0> =\", svD)\n", - "// fmt.Println(\"H|0> =\", svH)\n", - "//\n", - "// // Check measurement probabilities match\n", - "// p0D := real(svD[0])*real(svD[0]) + imag(svD[0])*imag(svD[0])\n", - "// p1D := real(svD[1])*real(svD[1]) + imag(svD[1])*imag(svD[1])\n", - "// p0H := real(svH[0])*real(svH[0]) + imag(svH[0])*imag(svH[0])\n", - "// p1H := real(svH[1])*real(svH[1]) + imag(svH[1])*imag(svH[1])\n", - "// fmt.Printf(\"\\nDecomposed: P(|0>)=%.4f, P(|1>)=%.4f\\n\", p0D, p1D)\n", - "// fmt.Printf(\"Hadamard: P(|0>)=%.4f, P(|1>)=%.4f\\n\", p0H, p1H)\n", - "// fmt.Println(\"Probabilities match:\", math.Abs(p0D-p0H) < 1e-10 && math.Abs(p1D-p1H) < 1e-10)\n", - "// fmt.Println(\"\\nDecomposed circuit:\")\n", - "// fmt.Println(draw.String(cDecomp))\n", - "fmt.Println(\"Uncomment the code above and fill in the rotation angles!\")" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the U3 parameters!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: Use U3 to produce various states on the Bloch sphere\n", - "// U3(theta, phi, lambda) is the most general single-qubit gate.\n", - "//\n", - "// TODO: Find the U3 parameters that produce each target state:\n", - "// State 1: |1> (south pole) -- Expected: P(|0>)=0, P(|1>)=1\n", - "// State 2: |+> (equator, +X axis) -- Expected: P(|0>)=0.5, P(|1>)=0.5\n", - "// State 3: |-i> = (|0> - i|1>)/sqrt(2) (equator, -Y axis) -- Expected: P(|0>)=0.5, P(|1>)=0.5\n", - "// State 4: An arbitrary point of your choice\n", - "//\n", - "// Hint: U3(theta, phi, lambda)|0> = cos(theta/2)|0> + e^(i*phi)*sin(theta/2)|1>\n", - "//\n", - "// c1, _ := builder.New(\"u3-ket1\", 1).U3( /* theta, phi, lambda */ , 0).Build()\n", - "// sim1 := statevector.New(1)\n", - "// sim1.Evolve(c1)\n", - "// sv1 := sim1.StateVector()\n", - "// fmt.Println(\"U3(...)|0> =\", sv1, \" (should be |1>)\")\n", - "//\n", - "// c2, _ := builder.New(\"u3-plus\", 1).U3( /* theta, phi, lambda */ , 0).Build()\n", - "// sim2 := statevector.New(1)\n", - "// sim2.Evolve(c2)\n", - "// sv2 := sim2.StateVector()\n", - "// fmt.Println(\"U3(...)|0> =\", sv2, \" (should be |+>)\")\n", - "//\n", - "// c3, _ := builder.New(\"u3-mi\", 1).U3( /* theta, phi, lambda */ , 0).Build()\n", - "// sim3 := statevector.New(1)\n", - "// sim3.Evolve(c3)\n", - "// sv3 := sim3.StateVector()\n", - "// fmt.Println(\"U3(...)|0> =\", sv3, \" (should be |-i>)\")\n", - "//\n", - "// c4, _ := builder.New(\"u3-arb\", 1).U3( /* theta, phi, lambda */ , 0).Build()\n", - "// sim4 := statevector.New(1)\n", - "// sim4.Evolve(c4)\n", - "// sv4 := sim4.StateVector()\n", - "// fmt.Printf(\"U3(...)|0> = [%.4f, %.4f]\\n\", sv4[0], sv4[1])\n", - "//\n", - "// gonbui.DisplayHTML(\n", - "// \t\"
\" +\n", - "// \t\tviz.Bloch(sv1, viz.WithTitle(\"|1>\"), viz.WithSize(280, 280)) +\n", - "// \t\tviz.Bloch(sv2, viz.WithTitle(\"|+>\"), viz.WithSize(280, 280)) +\n", - "// \t\tviz.Bloch(sv3, viz.WithTitle(\"|-i>\"), viz.WithSize(280, 280)) +\n", - "// \t\tviz.Bloch(sv4, viz.WithTitle(\"Arbitrary\"), viz.WithSize(280, 280)) +\n", - "// \t\t\"
\")\n", - "fmt.Println(\"Uncomment the code above and fill in the U3 parameters!\")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the U3 parameters!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: Use U3 to produce various states on the Bloch sphere\n", - "// U3(theta, phi, lambda) is the most general single-qubit gate.\n", - "//\n", - "// TODO: Find the U3 parameters that produce each target state:\n", - "// State 1: |1> (south pole)\n", - "// State 2: |+> (equator, +X axis)\n", - "// State 3: |-i> = (|0> - i|1>)/sqrt(2) (equator, -Y axis)\n", - "// State 4: An arbitrary point of your choice\n", - "//\n", - "// Hint: U3(theta, phi, lambda)|0> = cos(theta/2)|0> + e^(i*phi)*sin(theta/2)|1>\n", - "//\n", - "// c1, _ := builder.New(\"u3-ket1\", 1).U3( /* theta, phi, lambda */ , 0).Build()\n", - "// sim1 := statevector.New(1)\n", - "// sim1.Evolve(c1)\n", - "// sv1 := sim1.StateVector()\n", - "// fmt.Println(\"U3(...)|0> =\", sv1, \" (should be |1>)\")\n", - "//\n", - "// c2, _ := builder.New(\"u3-plus\", 1).U3( /* theta, phi, lambda */ , 0).Build()\n", - "// sim2 := statevector.New(1)\n", - "// sim2.Evolve(c2)\n", - "// sv2 := sim2.StateVector()\n", - "// fmt.Println(\"U3(...)|0> =\", sv2, \" (should be |+>)\")\n", - "//\n", - "// c3, _ := builder.New(\"u3-mi\", 1).U3( /* theta, phi, lambda */ , 0).Build()\n", - "// sim3 := statevector.New(1)\n", - "// sim3.Evolve(c3)\n", - "// sv3 := sim3.StateVector()\n", - "// fmt.Println(\"U3(...)|0> =\", sv3, \" (should be |-i>)\")\n", - "//\n", - "// c4, _ := builder.New(\"u3-arb\", 1).U3( /* theta, phi, lambda */ , 0).Build()\n", - "// sim4 := statevector.New(1)\n", - "// sim4.Evolve(c4)\n", - "// sv4 := sim4.StateVector()\n", - "// fmt.Printf(\"U3(...)|0> = [%.4f, %.4f]\\n\", sv4[0], sv4[1])\n", - "//\n", - "// gonbui.DisplayHTML(\n", - "// \t\"
\" +\n", - "// \t\tviz.Bloch(sv1, viz.WithTitle(\"|1>\"), viz.WithSize(280, 280)) +\n", - "// \t\tviz.Bloch(sv2, viz.WithTitle(\"|+>\"), viz.WithSize(280, 280)) +\n", - "// \t\tviz.Bloch(sv3, viz.WithTitle(\"|-i>\"), viz.WithSize(280, 280)) +\n", - "// \t\tviz.Bloch(sv4, viz.WithTitle(\"Arbitrary\"), viz.WithSize(280, 280)) +\n", - "// \t\t\"
\")\n", - "fmt.Println(\"Uncomment the code above and fill in the U3 parameters!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "\n", - "1. **Every single-qubit gate is a rotation** of the Bloch sphere, fully described by a 2x2 unitary matrix.\n", - "\n", - "2. **Pauli gates** (X, Y, Z) are 180-degree rotations and are self-inverse.\n", - "\n", - "3. **Hadamard** creates equal superposition and is the gateway to quantum parallelism.\n", - "\n", - "4. **Phase gates** form a hierarchy: T^2 = S, S^2 = Z. The T gate is essential for universality.\n", - "\n", - "5. **Rotation gates** (RX, RY, RZ) provide continuous rotations and are universal: any single-qubit unitary = RZ * RY * RZ (Euler decomposition).\n", - "\n", - "6. **U3(theta, phi, lambda)** is the most general single-qubit gate -- it can reach any point on the Bloch sphere from |0>.\n", - "\n", - "7. **Inverses** undo gates (gate followed by its inverse = identity). Use `g.Inverse()` in Goqu.\n", - "\n", - "8. **Powers** let you compose a gate with itself: `gate.Pow(g, k)` multiplies the matrix k times.\n", - "\n", - "In the next notebook, we will combine single-qubit gates with multi-qubit gates to create **entanglement** -- correlations that have no classical analog." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/notebooks/03-measurement.ipynb b/notebooks/03-measurement.ipynb deleted file mode 100644 index 58755b7..0000000 --- a/notebooks/03-measurement.ipynb +++ /dev/null @@ -1,795 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 03 - Measurement\n", - "\n", - "**Prerequisites:** Notebooks 01-02. Familiarity with qubits, gates, and statevector simulation.\n", - "\n", - "Measurement is where the quantum world meets the classical world. When we measure a qubit, we force it to reveal a definite classical value -- either 0 or 1 -- but this act **irreversibly destroys** the quantum superposition.\n", - "\n", - "In this notebook we explore:\n", - "- **Computational basis measurement** and the Born rule\n", - "- **Measurement collapse** -- the common misconception that \"measurement just reads the state\"\n", - "- **Mid-circuit measurement** with dynamic circuits and classical feed-forward\n", - "- **Partial measurement** on entangled states\n", - "- **The no-cloning theorem** -- why you can't copy an unknown quantum state\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Explain** the Born rule and predict measurement outcome probabilities.\n", - "2. **Demonstrate** that measurement collapses superposition using mid-circuit measurement.\n", - "3. **Describe** the no-cloning theorem and show why CNOT does not clone a superposition.\n", - "4. **Implement** dynamic circuits with classically conditioned gates." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"fmt\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/sim/statevector\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Computational Basis Measurement\n", - "\n", - "The simplest measurement is in the **computational basis** $\\{|0\\rangle, |1\\rangle\\}$. The **Born rule** tells us:\n", - "\n", - "$$P(\\text{outcome } k) = |\\langle k | \\psi \\rangle|^2$$\n", - "\n", - "If the qubit is already in a basis state, measurement is deterministic:\n", - "- Measure $|0\\rangle$ $\\rightarrow$ always 0\n", - "- Measure $|1\\rangle$ $\\rightarrow$ always 1\n", - "\n", - "Let's verify this with 1000 shots." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Circuit: measure |0>\n", - "|0> measurement results: map[0:1000]\n", - "\n", - "Circuit: measure |1>\n", - "|1> measurement results: map[1:1000]\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "X\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Measure |0> -- should always give 0\n", - "c0, _ := builder.New(\"measure-zero\", 1).MeasureAll().Build()\n", - "fmt.Println(\"Circuit: measure |0>\")\n", - "gonbui.DisplayHTML(draw.SVG(c0))\n", - "\n", - "sim := statevector.New(1)\n", - "counts0, _ := sim.Run(c0, 1000)\n", - "fmt.Printf(\"|0> measurement results: %v\\n\\n\", counts0)\n", - "\n", - "// Measure |1> -- apply X first, should always give 1\n", - "c1, _ := builder.New(\"measure-one\", 1).X(0).MeasureAll().Build()\n", - "fmt.Println(\"Circuit: measure |1>\")\n", - "gonbui.DisplayHTML(draw.SVG(c1))\n", - "\n", - "counts1, _ := sim.Run(c1, 1000)\n", - "fmt.Printf(\"|1> measurement results: %v\\n\", counts1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Measuring Superposition\n", - "\n", - "Things get interesting when we measure a qubit in superposition. Applying a Hadamard to $|0\\rangle$ creates:\n", - "\n", - "$$H|0\\rangle = \\frac{1}{\\sqrt{2}}(|0\\rangle + |1\\rangle) = |+\\rangle$$\n", - "\n", - "The Born rule gives us $P(0) = |\\frac{1}{\\sqrt{2}}|^2 = \\frac{1}{2}$ and $P(1) = \\frac{1}{2}$. We expect roughly 50/50 results." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "H\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "100\n", - "\n", - "200\n", - "\n", - "300\n", - "\n", - "400\n", - "\n", - "500\n", - "\n", - "600\n", - "\n", - "0\n", - "\n", - "1\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Measurement results: map[0:502 1:498]\n", - "P(0) = 0.502, P(1) = 0.498\n" - ] - } - ], - "source": [ - "%%\n", - "// H|0> then measure -- should be ~50/50\n", - "c, _ := builder.New(\"superposition\", 1).H(0).MeasureAll().Build()\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "\n", - "sim := statevector.New(1)\n", - "counts, _ := sim.Run(c, 1000)\n", - "fmt.Printf(\"Measurement results: %v\\n\", counts)\n", - "fmt.Printf(\"P(0) = %.3f, P(1) = %.3f\\n\",\n", - "\tfloat64(counts[\"0\"])/1000.0,\n", - "\tfloat64(counts[\"1\"])/1000.0)\n", - "\n", - "// Visualize as a histogram\n", - "gonbui.DisplayHTML(viz.Histogram(counts))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Measurement Collapse: Measurement Destroys Superposition\n", - "\n", - "A common misconception is that measurement simply \"reads\" the state without changing it. In reality, **measurement collapses the quantum state** into the observed basis state. After measurement, the qubit is no longer in superposition -- it is definitively $|0\\rangle$ or $|1\\rangle$.\n", - "\n", - "We can prove this using a **mid-circuit measurement**. Consider this sequence:\n", - "\n", - "1. $H|0\\rangle \\rightarrow |+\\rangle$ (create superposition)\n", - "2. **Measure** $\\rightarrow$ collapses to $|0\\rangle$ or $|1\\rangle$\n", - "3. $H$ again (apply Hadamard to the collapsed state)\n", - "4. **Measure** again\n", - "\n", - "If measurement didn't collapse the state, the second H would undo the first (since $HH = I$), and the second measurement would always give 0. But because measurement **destroys** the superposition, the second measurement's outcome depends on the first measurement's result:\n", - "\n", - "- If the first measurement gave 0: state is $|0\\rangle$, then $H|0\\rangle = |+\\rangle$, second measurement is 50/50\n", - "- If the first measurement gave 1: state is $|1\\rangle$, then $H|1\\rangle = |-\\rangle$, second measurement is also 50/50\n", - "\n", - "We use `If` to apply a classically conditioned gate and `RunDynamic` for mid-circuit measurement support." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Circuit (dynamic):\n", - "Is dynamic: true\n", - "\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "H\n", - "M\n", - "H\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Results (bit string = [c1 c0]):\n", - " 00: 526 (26.3%)\n", - " 11: 505 (25.2%)\n", - " 10: 480 (24.0%)\n", - " 01: 489 (24.4%)\n", - "\n", - "Key insight: all four outcomes (00, 01, 10, 11) appear.\n", - "If measurement didn't collapse the state, HH=I would make\n", - "the second measurement always match the first.\n", - "The presence of mismatched results (01, 10) proves collapse!\n" - ] - } - ], - "source": [ - "%%\n", - "// Build a dynamic circuit: H -> Measure -> H -> Measure\n", - "// Uses 2 classical bits to store both measurement results\n", - "c, _ := builder.New(\"collapse\", 1).WithClbits(2).\n", - "\tH(0). // Step 1: create |+>\n", - "\tMeasure(0, 0). // Step 2: measure -> collapses to |0> or |1>\n", - "\tH(0). // Step 3: H on collapsed state\n", - "\tMeasure(0, 1). // Step 4: measure again\n", - "\tBuild()\n", - "\n", - "fmt.Println(\"Circuit (dynamic):\")\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "fmt.Printf(\"Is dynamic: %v\\n\\n\", c.IsDynamic())\n", - "\n", - "sim := statevector.New(1)\n", - "counts, _ := sim.RunDynamic(c, 2000)\n", - "fmt.Println(\"Results (bit string = [c1 c0]):\")\n", - "for bs, n := range counts {\n", - "\tfmt.Printf(\" %s: %d (%.1f%%)\\n\", bs, n, float64(n)/2000.0*100)\n", - "}\n", - "\n", - "fmt.Println(\"\\nKey insight: all four outcomes (00, 01, 10, 11) appear.\")\n", - "fmt.Println(\"If measurement didn't collapse the state, HH=I would make\")\n", - "fmt.Println(\"the second measurement always match the first.\")\n", - "fmt.Println(\"The presence of mismatched results (01, 10) proves collapse!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Partial Measurement on Entangled States\n", - "\n", - "When qubits are entangled, measuring one instantly determines the other. Consider the Bell state:\n", - "\n", - "$$|\\Phi^+\\rangle = \\frac{1}{\\sqrt{2}}(|00\\rangle + |11\\rangle)$$\n", - "\n", - "If we measure only qubit 0:\n", - "- If we get 0, qubit 1 collapses to $|0\\rangle$\n", - "- If we get 1, qubit 1 collapses to $|1\\rangle$\n", - "\n", - "The qubits are perfectly correlated. Let's demonstrate by measuring qubit 0 first, then qubit 1 separately." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Partial measurement results:\n", - " 11: 492\n", - " 00: 508\n", - "\n", - "Only 00 and 11 appear -- measuring qubit 0 collapses qubit 1 too!\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "100\n", - "\n", - "200\n", - "\n", - "300\n", - "\n", - "400\n", - "\n", - "500\n", - "\n", - "600\n", - "\n", - "00\n", - "\n", - "11\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Bell state: measure only qubit 0 first, then qubit 1\n", - "c, _ := builder.New(\"partial-measure\", 2).WithClbits(2).\n", - "\tH(0).CNOT(0, 1). // Create Bell state |00> + |11>\n", - "\tMeasure(0, 0). // Measure only qubit 0\n", - "\tMeasure(1, 1). // Then measure qubit 1\n", - "\tBuild()\n", - "\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "\n", - "sim := statevector.New(2)\n", - "counts, _ := sim.RunDynamic(c, 1000)\n", - "fmt.Println(\"Partial measurement results:\")\n", - "for bs, n := range counts {\n", - "\tfmt.Printf(\" %s: %d\\n\", bs, n)\n", - "}\n", - "fmt.Println(\"\\nOnly 00 and 11 appear -- measuring qubit 0 collapses qubit 1 too!\")\n", - "\n", - "gonbui.DisplayHTML(viz.Histogram(counts))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** What happens if you measure a qubit **twice in a row** in the computational basis? Will the second measurement always agree with the first, or can it differ?\n", - "\n", - "**Pause and predict** before running the next cell.\n", - "\n", - "*Hint: Think about what the state looks like immediately after the first measurement.*" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "H\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Double measurement results (bit string = [c1 c0]):\n", - " 11: 500\n", - " 00: 500\n", - "\n", - "Only 00 and 11 appear -- both measurements always agree.\n", - "The second measurement is completely determined by the first.\n", - "This confirms: once collapsed, the state stays collapsed.\n" - ] - } - ], - "source": [ - "%%\n", - "// Start in superposition, then measure twice with no gates in between\n", - "c, _ := builder.New(\"double-measure\", 1).WithClbits(2).\n", - "\tH(0). // Create superposition\n", - "\tMeasure(0, 0). // First measurement -> collapses to |0> or |1>\n", - "\tMeasure(0, 1). // Second measurement -> should match the first\n", - "\tBuild()\n", - "\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "\n", - "sim := statevector.New(1)\n", - "counts, _ := sim.RunDynamic(c, 1000)\n", - "fmt.Println(\"Double measurement results (bit string = [c1 c0]):\")\n", - "for bs, n := range counts {\n", - "\tfmt.Printf(\" %s: %d\\n\", bs, n)\n", - "}\n", - "fmt.Println(\"\\nOnly 00 and 11 appear -- both measurements always agree.\")\n", - "fmt.Println(\"The second measurement is completely determined by the first.\")\n", - "fmt.Println(\"This confirms: once collapsed, the state stays collapsed.\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## The No-Cloning Theorem\n", - "\n", - "The **no-cloning theorem** states that it is impossible to create an identical copy of an arbitrary unknown quantum state. There is no unitary operation $U$ such that:\n", - "\n", - "$$U|\\psi\\rangle|0\\rangle = |\\psi\\rangle|\\psi\\rangle$$\n", - "\n", - "for all $|\\psi\\rangle$.\n", - "\n", - "A naive attempt might be to use a CNOT gate as a \"copier.\" CNOT works for basis states:\n", - "- $\\text{CNOT}|0\\rangle|0\\rangle = |0\\rangle|0\\rangle$ (copies $|0\\rangle$)\n", - "- $\\text{CNOT}|1\\rangle|0\\rangle = |1\\rangle|1\\rangle$ (copies $|1\\rangle$)\n", - "\n", - "But what about a superposition $|+\\rangle = \\frac{1}{\\sqrt{2}}(|0\\rangle + |1\\rangle)$?\n", - "\n", - "$$\\text{CNOT}|+\\rangle|0\\rangle = \\frac{1}{\\sqrt{2}}(|00\\rangle + |11\\rangle)$$\n", - "\n", - "This is a Bell state -- an **entangled** state, not the product state $|+\\rangle|+\\rangle = \\frac{1}{2}(|00\\rangle + |01\\rangle + |10\\rangle + |11\\rangle)$. The CNOT didn't copy the superposition; it created entanglement instead." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Attempting to clone |+> with CNOT:\n", - "Statevector after CNOT|+>|0>:\n", - " |00> : (0.7071+0.0000i) (prob = 0.50)\n", - " |11> : (0.7071+0.0000i) (prob = 0.50)\n", - "\n", - "If cloning worked, we'd see |00>, |01>, |10>, |11> each with prob 0.25\n", - "Instead we see only |00> and |11> -- this is a Bell state, not a clone!\n", - "\n", - "Measurement counts: map[00:493 11:507]\n", - "Correlations prove entanglement, not independent copies.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "100\n", - "\n", - "200\n", - "\n", - "300\n", - "\n", - "400\n", - "\n", - "500\n", - "\n", - "600\n", - "\n", - "00\n", - "\n", - "11\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Attempt to \"clone\" |+> using CNOT\n", - "c, _ := builder.New(\"clone-attempt\", 2).\n", - "\tH(0). // Prepare |+> on qubit 0\n", - "\tCNOT(0, 1). // \"Copy\" to qubit 1?\n", - "\tMeasureAll().\n", - "\tBuild()\n", - "\n", - "fmt.Println(\"Attempting to clone |+> with CNOT:\")\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "\n", - "sim := statevector.New(2)\n", - "\n", - "// First, look at the statevector before measurement\n", - "cNoMeas, _ := builder.New(\"clone-sv\", 2).H(0).CNOT(0, 1).Build()\n", - "sim.Evolve(cNoMeas)\n", - "sv := sim.StateVector()\n", - "fmt.Println(\"Statevector after CNOT|+>|0>:\")\n", - "for i, amp := range sv {\n", - "\tp := real(amp)*real(amp) + imag(amp)*imag(amp)\n", - "\tif p > 1e-10 {\n", - "\t\tfmt.Printf(\" |%02b> : %.4f (prob = %.2f)\\n\", i, amp, p)\n", - "\t}\n", - "}\n", - "\n", - "fmt.Println(\"\\nIf cloning worked, we'd see |00>, |01>, |10>, |11> each with prob 0.25\")\n", - "fmt.Println(\"Instead we see only |00> and |11> -- this is a Bell state, not a clone!\")\n", - "\n", - "// Run measurement to confirm\n", - "counts, _ := sim.Run(c, 1000)\n", - "fmt.Printf(\"\\nMeasurement counts: %v\\n\", counts)\n", - "fmt.Println(\"Correlations prove entanglement, not independent copies.\")\n", - "\n", - "gonbui.DisplayHTML(viz.Histogram(counts))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. If you measure a qubit in state |+⟩ and get 0, what state is the qubit in afterward?\n", - "2. Why can't you use CNOT to copy an unknown quantum state?\n", - "3. True or false: Measuring a qubit twice in a row (with no gates in between) can give different results. Why?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Exercises\n", - "\n", - "### Exercise 1: Quantum Coin Flip\n", - "\n", - "Design a \"quantum coin flip\" circuit -- a single qubit that produces a truly random 50/50 outcome. Run it for 1000 shots and display the histogram. This is the simplest quantum random number generator." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the gate!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: Quantum coin flip\n", - "// Build the simplest quantum random number generator:\n", - "// a single qubit that produces a truly random 50/50 outcome.\n", - "// Expected: approximately 500 \"0\" and 500 \"1\" (±50)\n", - "//\n", - "// TODO: Apply the right gate to |0> to create equal superposition, then measure\n", - "// coin, _ := builder.New(\"coin-flip\", 1). /* gate */ .MeasureAll().Build()\n", - "// fmt.Println(draw.String(coin))\n", - "//\n", - "// sim := statevector.New(1)\n", - "// flips, _ := sim.Run(coin, 1000)\n", - "// fmt.Printf(\"Heads (0): %d, Tails (1): %d\\n\", flips[\"0\"], flips[\"1\"])\n", - "//\n", - "// gonbui.DisplayHTML(viz.Histogram(flips))\n", - "fmt.Println(\"Uncomment the code above and fill in the gate!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Exercise 2: No-Cloning Verification\n", - "\n", - "Show that cloning is impossible: use CNOT to try to \"copy\" $|+\\rangle$ to a second qubit, then measure both. If cloning worked, both qubits would be independent and you'd see all four outcomes (00, 01, 10, 11) with equal probability. Instead, you should see only correlated outcomes." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and complete the no-cloning experiment!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: Verify the no-cloning theorem\n", - "// Use CNOT to try to \"copy\" |+> to a second qubit, then measure both.\n", - "// If cloning worked, you'd see all four outcomes (00, 01, 10, 11) equally.\n", - "// Instead, you should see only correlated outcomes (00 and 11).\n", - "// Expected: ~500 \"00\" and ~500 \"11\", with 0 counts for \"01\" and \"10\"\n", - "//\n", - "// TODO: Build the \"cloning\" circuit and an independent-copies circuit for comparison\n", - "// clone, _ := builder.New(\"no-clone\", 2).\n", - "// \tH(0). // prepare |+> on qubit 0\n", - "// \tCNOT(0, 1). // attempt to copy\n", - "// \tMeasureAll().\n", - "// \tBuild()\n", - "//\n", - "// sim := statevector.New(2)\n", - "// results, _ := sim.Run(clone, 1000)\n", - "//\n", - "// fmt.Println(\"CNOT 'cloning' results:\")\n", - "// for bs, n := range results {\n", - "// \tfmt.Printf(\" %s: %d (%.1f%%)\\n\", bs, n, float64(n)/1000.0*100)\n", - "// }\n", - "//\n", - "// // Check: are 01 and 10 present?\n", - "// if results[\"01\"] == 0 && results[\"10\"] == 0 {\n", - "// \tfmt.Println(\"\\nNo 01 or 10 outcomes -- qubits are perfectly correlated.\")\n", - "// \tfmt.Println(\"This is ENTANGLEMENT, not cloning. No-cloning theorem confirmed!\")\n", - "// }\n", - "//\n", - "// // Now build two truly independent |+> qubits for comparison\n", - "// indep, _ := builder.New(\"independent\", 2).\n", - "// \tH(0).H(1). // independent |+> on each qubit\n", - "// \tMeasureAll().\n", - "// \tBuild()\n", - "//\n", - "// sim2 := statevector.New(2)\n", - "// indepResults, _ := sim2.Run(indep, 1000)\n", - "// fmt.Println(\"\\nFor comparison, two independent |+> qubits (no CNOT):\")\n", - "// for bs, n := range indepResults {\n", - "// \tfmt.Printf(\" %s: %d (%.1f%%)\\n\", bs, n, float64(n)/1000.0*100)\n", - "// }\n", - "// fmt.Println(\"All four outcomes appear -- these are truly independent.\")\n", - "//\n", - "// gonbui.DisplayHTML(viz.Histogram(results))\n", - "fmt.Println(\"Uncomment the code above and complete the no-cloning experiment!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "\n", - "1. **Born rule**: The probability of measuring outcome $k$ is $|\\langle k|\\psi\\rangle|^2$. Basis states give deterministic results; superpositions give probabilistic results.\n", - "\n", - "2. **Measurement collapse**: Measurement is not a passive observation. It irreversibly destroys superposition, projecting the state onto the measured basis state. This is why quantum information is fundamentally different from classical information.\n", - "\n", - "3. **Repeated measurement is idempotent**: Measuring the same qubit twice without intervening gates always gives the same result. Once collapsed, the state stays collapsed.\n", - "\n", - "4. **Partial measurement and entanglement**: Measuring one qubit of an entangled pair instantaneously determines the other. This is the basis for quantum teleportation and superdense coding.\n", - "\n", - "5. **No-cloning theorem**: You cannot copy an unknown quantum state. CNOT appears to copy basis states but creates entanglement (not copies) when applied to superpositions. This is fundamental to quantum cryptography -- an eavesdropper cannot copy qubits without disturbing them.\n", - "\n", - "6. **Mid-circuit measurement**: Goqu supports dynamic circuits where measurements can occur in the middle of a circuit. The results can be used to conditionally apply gates via `If` and `IfBlock`, enabling feed-forward quantum computation." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/notebooks/04-multi-qubit-gates.ipynb b/notebooks/04-multi-qubit-gates.ipynb deleted file mode 100644 index 8888543..0000000 --- a/notebooks/04-multi-qubit-gates.ipynb +++ /dev/null @@ -1,1367 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "cell-0", - "metadata": {}, - "source": [ - "# 04 - Multi-Qubit Gates and Controlled Operations\n", - "\n", - "**Prerequisites:** Notebooks 01-03. Familiarity with single-qubit gates and measurement.\n", - "\n", - "Single-qubit gates can create superposition, but they cannot create\n", - "**entanglement** -- the defining resource of quantum computing. For that, we\n", - "need gates that act on two or more qubits simultaneously. Multi-qubit gates\n", - "introduce **conditional logic** into quantum circuits: one qubit's state\n", - "controls what happens to another.\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Describe** CNOT, CZ, SWAP, Toffoli, and Fredkin gates and their truth tables.\n", - "2. **Implement** controlled operations using gate.Controlled and builder.Ctrl.\n", - "3. **Verify** CZ symmetry and construct custom unitary gates.\n", - "4. **Explain** how multi-qubit gates are decomposed into simpler operations.\n", - "\n", - "In this notebook we will:\n", - "\n", - "1. Master the **CNOT** gate and build its full truth table.\n", - "2. Explore **CZ** symmetry -- why swapping control and target gives the same result.\n", - "3. Use the **SWAP** gate to exchange qubit states.\n", - "4. Study the **Toffoli (CCX)** gate as a reversible AND.\n", - "5. Demonstrate the **Fredkin (CSWAP)** gate as a controlled SWAP.\n", - "6. Build **controlled-U** and **multi-controlled** gates from the Goqu API.\n", - "7. Create **custom unitary** gates from user-defined matrices.\n", - "8. Examine **gate decomposition** into primitive operations.\n", - "9. Predict and verify the result of applying CNOT to a superposition state." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "cell-1", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"fmt\"\n", - "\t\"math\"\n", - "\t\"math/cmplx\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/circuit/gate\"\n", - "\t\"github.com/splch/goqu/sim/statevector\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-2", - "metadata": {}, - "source": [ - "## CNOT -- The Fundamental Two-Qubit Gate\n", - "\n", - "The **CNOT** (controlled-NOT, also called CX) gate flips the **target** qubit\n", - "if and only if the **control** qubit is |1>. It is the standard entangling\n", - "gate in quantum computing and, together with single-qubit gates, forms a\n", - "universal gate set.\n", - "\n", - "In our circuit `CNOT(0, 1)`, qubit 0 is the **control** and qubit 1 is the\n", - "**target**. Remember the bitstring convention: $|q_1\\,q_0\\rangle$ where qubit 0\n", - "is the **rightmost** bit.\n", - "\n", - "| Input | Output | Explanation |\n", - "|:-----:|:------:|:------------|\n", - "| \\|00⟩ | \\|00⟩ | q0=0 (control off), target q1 unchanged |\n", - "| \\|01⟩ | \\|11⟩ | q0=1 (control on), target q1 flipped 0→1 |\n", - "| \\|10⟩ | \\|10⟩ | q0=0 (control off), target q1 unchanged |\n", - "| \\|11⟩ | \\|01⟩ | q0=1 (control on), target q1 flipped 1→0 |\n", - "\n", - "Let's verify this truth table by evolving all four computational basis states." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "cell-3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CNOT Truth Table (control=q0, target=q1)\n", - "==========================================\n", - " Input -> Output\n", - " |00> -> |00>\n", - " |01> -> |11> <-- q0=1, target q1 flipped\n", - " |10> -> |10>\n", - " |11> -> |01> <-- q0=1, target q1 flipped\n", - "\n", - "Circuit for |01> -> |11>:\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "X\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// CNOT truth table: evolve all 4 basis states |00>, |01>, |10>, |11>\n", - "// Bitstring convention: |q1 q0> where q0 (rightmost) is the control\n", - "fmt.Println(\"CNOT Truth Table (control=q0, target=q1)\")\n", - "fmt.Println(\"==========================================\")\n", - "fmt.Println(\" Input -> Output\")\n", - "\n", - "for idx := 0; idx < 4; idx++ {\n", - "\tb := builder.New(\"cnot\", 2)\n", - "\t// idx bit k -> qubit k: bit 0 = q0, bit 1 = q1\n", - "\tif idx&1 == 1 {\n", - "\t\tb.X(0)\n", - "\t}\n", - "\tif (idx>>1)&1 == 1 {\n", - "\t\tb.X(1)\n", - "\t}\n", - "\tb.CNOT(0, 1)\n", - "\tc, _ := b.Build()\n", - "\n", - "\tsim := statevector.New(2)\n", - "\tsim.Evolve(c)\n", - "\tsv := sim.StateVector()\n", - "\n", - "\tfor outIdx, amp := range sv {\n", - "\t\tif real(amp)*real(amp)+imag(amp)*imag(amp) > 0.5 {\n", - "\t\t\tq0 := idx & 1\n", - "\t\t\tflipped := \"\"\n", - "\t\t\tif (idx>>1)&1 != (outIdx>>1)&1 {\n", - "\t\t\t\tflipped = fmt.Sprintf(\" <-- q0=%d, target q1 flipped\", q0)\n", - "\t\t\t}\n", - "\t\t\tfmt.Printf(\" |%02b> -> |%02b>%s\\n\", idx, outIdx, flipped)\n", - "\t\t}\n", - "\t}\n", - "}\n", - "\n", - "// Show the circuit diagram for CNOT(0,1) with q0=|1>\n", - "fmt.Println(\"\\nCircuit for |01> -> |11>:\")\n", - "cDemo, _ := builder.New(\"cnot-demo\", 2).X(0).CNOT(0, 1).Build()\n", - "gonbui.DisplayHTML(draw.SVG(cDemo))" - ] - }, - { - "cell_type": "markdown", - "id": "cell-4", - "metadata": {}, - "source": [ - "## CZ Gate and Its Symmetry\n", - "\n", - "The **CZ** (controlled-Z) gate applies a Z gate to the target when the control\n", - "is |1>. Its matrix is `diag(1, 1, 1, -1)` -- it flips the phase of |11> to\n", - "-|11> and leaves all other basis states unchanged.\n", - "\n", - "A remarkable property of CZ is that it is **symmetric**: swapping the control\n", - "and target qubits gives the same result. This is because the matrix is\n", - "diagonal, so CZ(0,1) = CZ(1,0). Let's verify this." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "cell-5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CZ Symmetry Demonstration\n", - "=========================\n", - "Input state: H|0> tensor X|0> = |+1>\n", - "\n", - "CZ(0,1) result:\n", - " |10> : +0.7071\n", - " |11> : -0.7071\n", - "\n", - "CZ(1,0) result:\n", - " |10> : +0.7071\n", - " |11> : -0.7071\n", - "\n", - "CZ(0,1) == CZ(1,0): true\n", - "\n", - "CZ is symmetric because its matrix is diagonal:\n", - " diag(1, 1, 1, -1)\n", - "Only |11> picks up a phase of -1, regardless of which qubit is 'control'.\n" - ] - } - ], - "source": [ - "%%\n", - "// CZ symmetry: CZ(0,1) == CZ(1,0) on the same input\n", - "// Prepare |+1> = H(0) X(1) and apply CZ both ways\n", - "fmt.Println(\"CZ Symmetry Demonstration\")\n", - "fmt.Println(\"=========================\")\n", - "\n", - "// CZ with q0 as control\n", - "c01, _ := builder.New(\"cz-01\", 2).H(0).X(1).CZ(0, 1).Build()\n", - "sim01 := statevector.New(2)\n", - "sim01.Evolve(c01)\n", - "sv01 := sim01.StateVector()\n", - "\n", - "// CZ with q1 as control\n", - "c10, _ := builder.New(\"cz-10\", 2).H(0).X(1).CZ(1, 0).Build()\n", - "sim10 := statevector.New(2)\n", - "sim10.Evolve(c10)\n", - "sv10 := sim10.StateVector()\n", - "\n", - "fmt.Println(\"Input state: H|0> tensor X|0> = |+1>\")\n", - "fmt.Println()\n", - "fmt.Println(\"CZ(0,1) result:\")\n", - "for i, amp := range sv01 {\n", - "\tif real(amp)*real(amp)+imag(amp)*imag(amp) > 1e-10 {\n", - "\t\tfmt.Printf(\" |%02b> : %+.4f\\n\", i, real(amp))\n", - "\t}\n", - "}\n", - "\n", - "fmt.Println(\"\\nCZ(1,0) result:\")\n", - "for i, amp := range sv10 {\n", - "\tif real(amp)*real(amp)+imag(amp)*imag(amp) > 1e-10 {\n", - "\t\tfmt.Printf(\" |%02b> : %+.4f\\n\", i, real(amp))\n", - "\t}\n", - "}\n", - "\n", - "// Verify they are identical\n", - "identical := true\n", - "for i := range sv01 {\n", - "\tif cmplx.Abs(sv01[i]-sv10[i]) > 1e-10 {\n", - "\t\tidentical = false\n", - "\t\tbreak\n", - "\t}\n", - "}\n", - "fmt.Printf(\"\\nCZ(0,1) == CZ(1,0): %v\\n\", identical)\n", - "fmt.Println(\"\\nCZ is symmetric because its matrix is diagonal:\")\n", - "fmt.Println(\" diag(1, 1, 1, -1)\")\n", - "fmt.Println(\"Only |11> picks up a phase of -1, regardless of which qubit is 'control'.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-6", - "metadata": {}, - "source": [ - "## SWAP Gate\n", - "\n", - "The **SWAP** gate exchanges the states of two qubits:\n", - "- |01⟩ becomes |10⟩ (qubit 0 was on, now qubit 1 is on)\n", - "- |10⟩ becomes |01⟩\n", - "- |00⟩ and |11⟩ are unchanged\n", - "\n", - "SWAP is equivalent to three consecutive CNOTs: CNOT(0,1), CNOT(1,0), CNOT(0,1).\n", - "The transpiler inserts SWAPs to route qubits on hardware with limited connectivity." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "cell-7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "SWAP Circuit:\n", - "Input: |10> (qubit 1 set)\n", - "Output statevector:\n", - " |01> : (1.0000+0.0000i)\n", - "\n", - "SWAP exchanged qubit states: |10> -> |01>\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "X\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// SWAP demonstration: prepare |10> (qubit 1 set), SWAP -> |01> (qubit 0 set)\n", - "cSwap, _ := builder.New(\"swap-demo\", 2).\n", - "\tX(1). // set qubit 1 -> state |10>\n", - "\tSWAP(0, 1). // SWAP -> should give |01>\n", - "\tBuild()\n", - "\n", - "fmt.Println(\"SWAP Circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(cSwap))\n", - "\n", - "sim := statevector.New(2)\n", - "sim.Evolve(cSwap)\n", - "sv := sim.StateVector()\n", - "\n", - "fmt.Println(\"Input: |10> (qubit 1 set)\")\n", - "fmt.Println(\"Output statevector:\")\n", - "for i, amp := range sv {\n", - "\tif real(amp)*real(amp)+imag(amp)*imag(amp) > 1e-10 {\n", - "\t\tfmt.Printf(\" |%02b> : %.4f\\n\", i, amp)\n", - "\t}\n", - "}\n", - "fmt.Println(\"\\nSWAP exchanged qubit states: |10> -> |01>\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-8", - "metadata": {}, - "source": [ - "## Toffoli Gate (CCX) -- Reversible AND\n", - "\n", - "The **Toffoli gate** (also called CCX or doubly-controlled NOT) flips the\n", - "target qubit if and only if **both** control qubits are |1>. It is the\n", - "quantum analog of the classical AND gate and is **universal for reversible\n", - "classical computation**: any classical Boolean function can be built from\n", - "Toffoli gates alone.\n", - "\n", - "In our circuit `CCX(0, 1, 2)`, qubits 0 and 1 are the controls and qubit 2\n", - "is the target. In the bitstring $|q_2\\,q_1\\,q_0\\rangle$, the controls are\n", - "the two rightmost bits. The target flips only when $q_0 = 1$ AND $q_1 = 1$." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "cell-9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Toffoli (CCX) Truth Table (controls=q0,q1; target=q2)\n", - "======================================================\n", - " Input -> Output\n", - " |000> -> |000>\n", - " |001> -> |001>\n", - " |010> -> |010>\n", - " |011> -> |111> <-- target flipped\n", - " |100> -> |100>\n", - " |101> -> |101>\n", - " |110> -> |110>\n", - " |111> -> |011> <-- target flipped\n", - "\n", - "The target flips only when BOTH controls are |1>.\n", - "If q2 starts as |0>, the output q2 = q0 AND q1 (reversible AND).\n", - "\n", - "Circuit for |011> -> |111>:\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "X\n", - "X\n", - "\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// CCX (Toffoli) truth table: evolve all 8 basis states for 3 qubits\n", - "// Bitstring convention: |q2 q1 q0> where q0 and q1 are controls, q2 is target\n", - "fmt.Println(\"Toffoli (CCX) Truth Table (controls=q0,q1; target=q2)\")\n", - "fmt.Println(\"======================================================\")\n", - "fmt.Println(\" Input -> Output\")\n", - "\n", - "for idx := 0; idx < 8; idx++ {\n", - "\tb := builder.New(\"ccx\", 3)\n", - "\t// idx bit k -> qubit k\n", - "\tif idx&1 == 1 {\n", - "\t\tb.X(0)\n", - "\t}\n", - "\tif (idx>>1)&1 == 1 {\n", - "\t\tb.X(1)\n", - "\t}\n", - "\tif (idx>>2)&1 == 1 {\n", - "\t\tb.X(2)\n", - "\t}\n", - "\tb.CCX(0, 1, 2)\n", - "\tc, _ := b.Build()\n", - "\n", - "\tsim := statevector.New(3)\n", - "\tsim.Evolve(c)\n", - "\tsv := sim.StateVector()\n", - "\n", - "\tfor outIdx, amp := range sv {\n", - "\t\tif real(amp)*real(amp)+imag(amp)*imag(amp) > 0.5 {\n", - "\t\t\tflipped := \"\"\n", - "\t\t\tif (idx>>2)&1 != (outIdx>>2)&1 {\n", - "\t\t\t\tflipped = \" <-- target flipped\"\n", - "\t\t\t}\n", - "\t\t\tfmt.Printf(\" |%03b> -> |%03b>%s\\n\", idx, outIdx, flipped)\n", - "\t\t}\n", - "\t}\n", - "}\n", - "\n", - "fmt.Println(\"\\nThe target flips only when BOTH controls are |1>.\")\n", - "fmt.Println(\"If q2 starts as |0>, the output q2 = q0 AND q1 (reversible AND).\")\n", - "\n", - "// Show the circuit\n", - "cCCXDemo, _ := builder.New(\"ccx\", 3).X(0).X(1).CCX(0, 1, 2).Build()\n", - "fmt.Println(\"\\nCircuit for |011> -> |111>:\")\n", - "gonbui.DisplayHTML(draw.SVG(cCCXDemo))" - ] - }, - { - "cell_type": "markdown", - "id": "cell-10", - "metadata": {}, - "source": [ - "## Fredkin Gate (CSWAP) -- Controlled SWAP\n", - "\n", - "The **Fredkin gate** (CSWAP) swaps q1 and q2 if and only if the control q0\n", - "is |1>. Like the Toffoli gate, it is universal for reversible classical\n", - "computation. It is self-inverse: applying CSWAP twice returns to the\n", - "original state.\n", - "\n", - "In our circuit `Apply(gate.CSWAP, 0, 1, 2)`, qubit 0 is the control and\n", - "qubits 1 and 2 are the swap targets. When q0=0, the targets are untouched;\n", - "when q0=1, the targets swap." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "cell-11", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CSWAP (Fredkin) Demonstration\n", - "=============================\n", - "\n", - "Test 1: Control q0=0, targets q1=0, q2=1 (state |100>)\n", - " Result: |100> -- targets NOT swapped (control is off)\n", - "\n", - "Test 2: Control q0=1, targets q1=0, q2=1 (state |101>)\n", - " Result: |011> -- targets SWAPPED (q1 and q2 exchanged)\n", - "\n", - "Circuit for control=|1> case:\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "X\n", - "X\n", - "\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// CSWAP (Fredkin) demonstration\n", - "// Test 1: control q0=|0>, q1=|0>, q2=|1> -> should NOT swap\n", - "cNoSwap, _ := builder.New(\"cswap-no\", 3).\n", - "\tX(2). // set q2 -> state |100>\n", - "\tApply(gate.CSWAP, 0, 1, 2). // control=q0=|0>, should not swap\n", - "\tBuild()\n", - "\n", - "simNo := statevector.New(3)\n", - "simNo.Evolve(cNoSwap)\n", - "svNo := simNo.StateVector()\n", - "\n", - "fmt.Println(\"CSWAP (Fredkin) Demonstration\")\n", - "fmt.Println(\"=============================\")\n", - "fmt.Println(\"\\nTest 1: Control q0=0, targets q1=0, q2=1 (state |100>)\")\n", - "for i, amp := range svNo {\n", - "\tif real(amp)*real(amp)+imag(amp)*imag(amp) > 0.5 {\n", - "\t\tfmt.Printf(\" Result: |%03b> -- targets NOT swapped (control is off)\\n\", i)\n", - "\t}\n", - "}\n", - "\n", - "// Test 2: control q0=|1>, q1=|0>, q2=|1> -> should swap q1 and q2\n", - "cYesSwap, _ := builder.New(\"cswap-yes\", 3).\n", - "\tX(0). // set control q0 to |1>\n", - "\tX(2). // set q2 to |1> -> state |101>\n", - "\tApply(gate.CSWAP, 0, 1, 2). // control=q0=|1>, swap q1 and q2\n", - "\tBuild()\n", - "\n", - "simYes := statevector.New(3)\n", - "simYes.Evolve(cYesSwap)\n", - "svYes := simYes.StateVector()\n", - "\n", - "fmt.Println(\"\\nTest 2: Control q0=1, targets q1=0, q2=1 (state |101>)\")\n", - "for i, amp := range svYes {\n", - "\tif real(amp)*real(amp)+imag(amp)*imag(amp) > 0.5 {\n", - "\t\tfmt.Printf(\" Result: |%03b> -- targets SWAPPED (q1 and q2 exchanged)\\n\", i)\n", - "\t}\n", - "}\n", - "\n", - "fmt.Println(\"\\nCircuit for control=|1> case:\")\n", - "gonbui.DisplayHTML(draw.SVG(cYesSwap))" - ] - }, - { - "cell_type": "markdown", - "id": "cell-12", - "metadata": {}, - "source": [ - "## Controlled-U and Multi-Controlled Gates\n", - "\n", - "Goqu provides flexible tools for building controlled operations:\n", - "\n", - "- **`gate.Controlled(inner, nControls)`** wraps any gate with control qubits.\n", - " The gate fires only when all control qubits are |1>.\n", - "- **`builder.Ctrl(g, controls, targets...)`** is the fluent builder version.\n", - "- **`builder.MCX(controls, target)`** is a shortcut for multi-controlled X.\n", - "\n", - "For example, `gate.Controlled(gate.H, 1)` creates a controlled-Hadamard,\n", - "and `builder.Ctrl(gate.RY(pi/4), []int{0,1}, 2)` creates a doubly-controlled\n", - "RY gate." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "cell-13", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Controlled-H Demonstration\n", - "==========================\n", - "Control=|0>: target stays |0>\n", - " |00> : (1.0000+0.0000i) (prob=1.0000)\n", - "\n", - "Control=|1>: target enters superposition\n", - " |01> : (0.7071+0.0000i) (prob=0.5000)\n", - " |11> : (0.7071+0.0000i) (prob=0.5000)\n", - "\n", - "Circuit (control=|1> case):\n", - "\n", - "Multi-Controlled RY(pi/4) with 2 controls\n", - "==========================================\n", - "Both controls=|1>, target gets RY(pi/4):\n", - " |011> : (+0.9239+0.0000i) (prob=0.8536)\n", - " |111> : (+0.3827+0.0000i) (prob=0.1464)\n", - "\n", - "Circuit:\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "X\n", - "\n", - "\n", - "H\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "X\n", - "X\n", - "\n", - "\n", - "\n", - "RY(pi/4)\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Controlled-H: apply H to target only when control is |1>\n", - "fmt.Println(\"Controlled-H Demonstration\")\n", - "fmt.Println(\"==========================\")\n", - "\n", - "// Case 1: control=|0> -- target should remain |0>\n", - "cCH0, _ := builder.New(\"ch-0\", 2).\n", - "\tCtrl(gate.H, []int{0}, 1).\n", - "\tBuild()\n", - "sim0 := statevector.New(2)\n", - "sim0.Evolve(cCH0)\n", - "sv0 := sim0.StateVector()\n", - "fmt.Println(\"Control=|0>: target stays |0>\")\n", - "for i, amp := range sv0 {\n", - "\tp := real(amp)*real(amp) + imag(amp)*imag(amp)\n", - "\tif p > 1e-10 {\n", - "\t\tfmt.Printf(\" |%02b> : %.4f (prob=%.4f)\\n\", i, amp, p)\n", - "\t}\n", - "}\n", - "\n", - "// Case 2: control=|1> -- target enters superposition\n", - "cCH1, _ := builder.New(\"ch-1\", 2).\n", - "\tX(0). // set control to |1>\n", - "\tCtrl(gate.H, []int{0}, 1). // controlled-H on target\n", - "\tBuild()\n", - "sim1 := statevector.New(2)\n", - "sim1.Evolve(cCH1)\n", - "sv1 := sim1.StateVector()\n", - "fmt.Println(\"\\nControl=|1>: target enters superposition\")\n", - "for i, amp := range sv1 {\n", - "\tp := real(amp)*real(amp) + imag(amp)*imag(amp)\n", - "\tif p > 1e-10 {\n", - "\t\tfmt.Printf(\" |%02b> : %.4f (prob=%.4f)\\n\", i, amp, p)\n", - "\t}\n", - "}\n", - "\n", - "fmt.Println(\"\\nCircuit (control=|1> case):\")\n", - "gonbui.DisplayHTML(draw.SVG(cCH1))\n", - "\n", - "// Multi-controlled RY: controls=[0,1], target=2\n", - "fmt.Println(\"\\nMulti-Controlled RY(pi/4) with 2 controls\")\n", - "fmt.Println(\"==========================================\")\n", - "cMCRY, _ := builder.New(\"mc-ry\", 3).\n", - "\tX(0).X(1). // set both controls to |1>\n", - "\tCtrl(gate.RY(math.Pi/4), []int{0, 1}, 2).\n", - "\tBuild()\n", - "\n", - "simMCRY := statevector.New(3)\n", - "simMCRY.Evolve(cMCRY)\n", - "svMCRY := simMCRY.StateVector()\n", - "\n", - "fmt.Println(\"Both controls=|1>, target gets RY(pi/4):\")\n", - "for i, amp := range svMCRY {\n", - "\tp := real(amp)*real(amp) + imag(amp)*imag(amp)\n", - "\tif p > 1e-10 {\n", - "\t\tfmt.Printf(\" |%03b> : %+.4f (prob=%.4f)\\n\", i, amp, p)\n", - "\t}\n", - "}\n", - "fmt.Println(\"\\nCircuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(cMCRY))" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "cell-14", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "MCX (Multi-Controlled X) with 3 Controls\n", - "=========================================\n", - "Input: |0111> (3 controls q0,q1,q2 = |1>, target q3 = |0>)\n", - " Output: |1111> -- target flipped to |1>\n", - "\n", - "Input: |0011> (only 2 of 3 controls = |1>)\n", - " Output: |0011> -- target unchanged (not all controls active)\n", - "\n", - "Circuit for 3-controlled X:\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "q3\n", - "\n", - "X\n", - "X\n", - "X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// MCX: Multi-controlled X with 3 controls on a 4-qubit circuit\n", - "fmt.Println(\"MCX (Multi-Controlled X) with 3 Controls\")\n", - "fmt.Println(\"=========================================\")\n", - "\n", - "// All controls |1> -> target flips\n", - "cMCX, _ := builder.New(\"mcx\", 4).\n", - "\tX(0).X(1).X(2). // set all 3 controls to |1>\n", - "\tMCX([]int{0, 1, 2}, 3). // multi-controlled X on target q3\n", - "\tBuild()\n", - "\n", - "simMCX := statevector.New(4)\n", - "simMCX.Evolve(cMCX)\n", - "svMCX := simMCX.StateVector()\n", - "\n", - "fmt.Println(\"Input: |0111> (3 controls q0,q1,q2 = |1>, target q3 = |0>)\")\n", - "for i, amp := range svMCX {\n", - "\tif real(amp)*real(amp)+imag(amp)*imag(amp) > 0.5 {\n", - "\t\tfmt.Printf(\" Output: |%04b> -- target flipped to |1>\\n\", i)\n", - "\t}\n", - "}\n", - "\n", - "// Only 2 of 3 controls |1> -> target stays\n", - "cMCX2, _ := builder.New(\"mcx-partial\", 4).\n", - "\tX(0).X(1). // only 2 controls are |1>\n", - "\tMCX([]int{0, 1, 2}, 3). // target should NOT flip\n", - "\tBuild()\n", - "\n", - "simMCX2 := statevector.New(4)\n", - "simMCX2.Evolve(cMCX2)\n", - "svMCX2 := simMCX2.StateVector()\n", - "\n", - "fmt.Println(\"\\nInput: |0011> (only 2 of 3 controls = |1>)\")\n", - "for i, amp := range svMCX2 {\n", - "\tif real(amp)*real(amp)+imag(amp)*imag(amp) > 0.5 {\n", - "\t\tfmt.Printf(\" Output: |%04b> -- target unchanged (not all controls active)\\n\", i)\n", - "\t}\n", - "}\n", - "\n", - "fmt.Println(\"\\nCircuit for 3-controlled X:\")\n", - "gonbui.DisplayHTML(draw.SVG(cMCX))" - ] - }, - { - "cell_type": "markdown", - "id": "cell-15", - "metadata": {}, - "source": [ - "## Custom Unitary Gates\n", - "\n", - "When the standard gate library does not have the gate you need, Goqu lets you\n", - "create one from a raw unitary matrix using `gate.MustUnitary(name, matrix)`.\n", - "The matrix must be:\n", - "- **Square**: 2x2 (1 qubit), 4x4 (2 qubits), or 8x8 (3 qubits)\n", - "- **Unitary**: U dagger U = I (verified automatically)\n", - "- **Flat row-major**: `[]complex128` of length 4, 16, or 64\n", - "\n", - "Let's create a custom sqrt(X) gate whose matrix is:\n", - "\n", - "$$\\sqrt{X} = \\frac{1}{2}\\begin{pmatrix} 1+i & 1-i \\\\ 1-i & 1+i \\end{pmatrix}$$\n", - "\n", - "Applying it twice should equal the X gate." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "cell-16", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Custom sqrt(X) Gate\n", - "===================\n", - "Name: sqrtX\n", - "Qubits: 1\n", - "Matrix: [(0.5+0.5i) (0.5-0.5i) (0.5-0.5i) (0.5+0.5i)]\n", - "\n", - "sqrt(X)|0> = [(0.5+0.5i) (0.5-0.5i)]\n", - "sqrt(X)^2 |0> = [(0+0i) (1+0i)]\n", - "\n", - "P(|0>) = 0.0000, P(|1>) = 1.0000\n", - "sqrt(X) applied twice gives |1> (= X|0>): true\n", - "\n", - "Circuit:\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "sqrtX\n", - "sqrtX\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Custom unitary: sqrt(X) gate\n", - "sqrtXMatrix := []complex128{\n", - "\tcomplex(0.5, 0.5), complex(0.5, -0.5),\n", - "\tcomplex(0.5, -0.5), complex(0.5, 0.5),\n", - "}\n", - "sqrtX := gate.MustUnitary(\"sqrtX\", sqrtXMatrix)\n", - "\n", - "fmt.Println(\"Custom sqrt(X) Gate\")\n", - "fmt.Println(\"===================\")\n", - "fmt.Printf(\"Name: %s\\n\", sqrtX.Name())\n", - "fmt.Printf(\"Qubits: %d\\n\", sqrtX.Qubits())\n", - "fmt.Println(\"Matrix:\", sqrtX.Matrix())\n", - "\n", - "// Apply sqrt(X) to |0>\n", - "cSqrtX, _ := builder.New(\"sqrtx\", 1).Apply(sqrtX, 0).Build()\n", - "simSX := statevector.New(1)\n", - "simSX.Evolve(cSqrtX)\n", - "svSX := simSX.StateVector()\n", - "fmt.Println(\"\\nsqrt(X)|0> =\", svSX)\n", - "\n", - "// Apply sqrt(X) twice -- should equal X|0> = |1>\n", - "cSqrtX2, _ := builder.New(\"sqrtx2\", 1).\n", - "\tApply(sqrtX, 0).\n", - "\tApply(sqrtX, 0).\n", - "\tBuild()\n", - "simSX2 := statevector.New(1)\n", - "simSX2.Evolve(cSqrtX2)\n", - "svSX2 := simSX2.StateVector()\n", - "fmt.Println(\"sqrt(X)^2 |0> =\", svSX2)\n", - "\n", - "// Verify: sqrt(X)^2 = X (up to global phase)\n", - "p0 := real(svSX2[0])*real(svSX2[0]) + imag(svSX2[0])*imag(svSX2[0])\n", - "p1 := real(svSX2[1])*real(svSX2[1]) + imag(svSX2[1])*imag(svSX2[1])\n", - "fmt.Printf(\"\\nP(|0>) = %.4f, P(|1>) = %.4f\\n\", p0, p1)\n", - "fmt.Println(\"sqrt(X) applied twice gives |1> (= X|0>):\", p1 > 0.999)\n", - "\n", - "fmt.Println(\"\\nCircuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(cSqrtX2))" - ] - }, - { - "cell_type": "markdown", - "id": "cell-17", - "metadata": {}, - "source": [ - "## Gate Decomposition\n", - "\n", - "Real quantum hardware can only execute a small set of **native gates** (e.g.,\n", - "CNOT + single-qubit rotations on IBM, or CZ + single-qubit on Google). Any\n", - "higher-level gate must be **decomposed** into these primitives.\n", - "\n", - "For example, the Toffoli gate (CCX) requires 6 CNOTs and several single-qubit\n", - "gates when decomposed for hardware. The Goqu `Gate.Decompose(qubits)` method\n", - "returns such a decomposition when available.\n", - "\n", - "Let's examine the matrices of multi-qubit gates to understand their structure\n", - "and verify that controlled gates are identity except in the subspace where\n", - "all controls are |1>." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "cell-18", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Gate Matrix Structure\n", - "=====================\n", - "\n", - "CNOT (2-qubit, 4x4 matrix):\n", - " [ 1.0 . . . ]\n", - " [ . 1.0 . . ]\n", - " [ . . . 1.0 ]\n", - " [ . . 1.0 . ]\n", - "\n", - "CZ (2-qubit, 4x4 matrix):\n", - " [ 1.0 . . . ]\n", - " [ . 1.0 . . ]\n", - " [ . . 1.0 . ]\n", - " [ . . . -1.0 ]\n", - "\n", - "SWAP (2-qubit, 4x4 matrix):\n", - " [ 1.0 . . . ]\n", - " [ . . 1.0 . ]\n", - " [ . 1.0 . . ]\n", - " [ . . . 1.0 ]\n", - "\n", - "CCX (Toffoli) structure:\n", - "The 8x8 matrix is identity except rows/cols 6-7,\n", - "where the X gate acts (swaps |110> and |111>):\n", - "\n", - "CCX (3-qubit, 8x8 matrix):\n", - " [ 1.0 . . . . . . . ]\n", - " [ . 1.0 . . . . . . ]\n", - " [ . . 1.0 . . . . . ]\n", - " [ . . . 1.0 . . . . ]\n", - " [ . . . . 1.0 . . . ]\n", - " [ . . . . . 1.0 . . ]\n", - " [ . . . . . . . 1.0 ]\n", - " [ . . . . . . 1.0 . ]\n", - "\n", - "Controlled(H, 1) name: C1-H, qubits: 2\n", - "\n", - "Controlled-H (2-qubit, 4x4 matrix):\n", - " [ 1.0 . . . ]\n", - " [ . 1.0 . . ]\n", - " [ . . 0.7 0.7 ]\n", - " [ . . 0.7 -0.7 ]\n" - ] - } - ], - "source": [ - "%%\n", - "// Examine the structure of multi-qubit gate matrices\n", - "fmt.Println(\"Gate Matrix Structure\")\n", - "fmt.Println(\"=====================\")\n", - "\n", - "// Helper to print a gate matrix\n", - "printMatrix := func(name string, g gate.Gate) {\n", - "\tm := g.Matrix()\n", - "\tdim := 1 << g.Qubits()\n", - "\tfmt.Printf(\"\\n%s (%d-qubit, %dx%d matrix):\\n\", name, g.Qubits(), dim, dim)\n", - "\tfor r := 0; r < dim; r++ {\n", - "\t\tfmt.Print(\" [\")\n", - "\t\tfor c := 0; c < dim; c++ {\n", - "\t\t\tv := m[r*dim+c]\n", - "\t\t\tif cmplx.Abs(v) < 1e-10 {\n", - "\t\t\t\tfmt.Print(\" . \")\n", - "\t\t\t} else if imag(v) == 0 {\n", - "\t\t\t\tfmt.Printf(\"%5.1f\", real(v))\n", - "\t\t\t} else {\n", - "\t\t\t\tfmt.Printf(\"%5.1f\", v)\n", - "\t\t\t}\n", - "\t\t}\n", - "\t\tfmt.Println(\" ]\")\n", - "\t}\n", - "}\n", - "\n", - "printMatrix(\"CNOT\", gate.CNOT)\n", - "printMatrix(\"CZ\", gate.CZ)\n", - "printMatrix(\"SWAP\", gate.SWAP)\n", - "\n", - "// Show that CCX is identity except the bottom-right 2x2 block\n", - "fmt.Println(\"\\nCCX (Toffoli) structure:\")\n", - "fmt.Println(\"The 8x8 matrix is identity except rows/cols 6-7,\")\n", - "fmt.Println(\"where the X gate acts (swaps |110> and |111>):\")\n", - "printMatrix(\"CCX\", gate.CCX)\n", - "\n", - "// Demonstrate gate.Controlled builds the right matrix\n", - "ch := gate.Controlled(gate.H, 1)\n", - "fmt.Printf(\"\\nControlled(H, 1) name: %s, qubits: %d\\n\", ch.Name(), ch.Qubits())\n", - "printMatrix(\"Controlled-H\", ch)" - ] - }, - { - "cell_type": "markdown", - "id": "cell-19", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** What does CNOT do to the state $|+0\\rangle$? (That is, H on qubit 0, then CNOT with control=q0, target=q1.)\n", - "\n", - "**Pause and predict** before reading further. The state $|+0\\rangle = (|00\\rangle + |01\\rangle)/\\sqrt{2}$ has qubit 0 in superposition and qubit 1 in $|0\\rangle$. Remember that in our bitstring notation $|q_1\\,q_0\\rangle$, qubit 0 is the rightmost bit.\n", - "\n", - "*Hint: The CNOT flips q1 when q0=1. Think about what happens to each term in the superposition separately.*" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "cell-20", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Circuit: H(0) then CNOT(0,1)\n", - "Statevector:\n", - " |00> : +0.7071\n", - " |11> : +0.7071\n", - "\n", - "Prediction confirmed: CNOT|+0> = (|00> + |11>)/sqrt(2) = Bell state |Phi+>\n", - "\n", - "Measurement results (1000 shots):\n", - " |00> : 487 (48.7%)\n", - " |11> : 513 (51.3%)\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "Bell State |Phi+> from CNOT|+0>\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "100\n", - "\n", - "200\n", - "\n", - "300\n", - "\n", - "400\n", - "\n", - "500\n", - "\n", - "600\n", - "\n", - "00\n", - "\n", - "11\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Predict-then-verify: CNOT on |+0> creates a Bell state\n", - "cBell, _ := builder.New(\"bell\", 2).\n", - "\tH(0). // |0> -> |+> on q0\n", - "\tCNOT(0, 1). // entangle\n", - "\tBuild()\n", - "\n", - "fmt.Println(\"Circuit: H(0) then CNOT(0,1)\")\n", - "gonbui.DisplayHTML(draw.SVG(cBell))\n", - "\n", - "simBell := statevector.New(2)\n", - "simBell.Evolve(cBell)\n", - "svBell := simBell.StateVector()\n", - "\n", - "fmt.Println(\"Statevector:\")\n", - "for i, amp := range svBell {\n", - "\tif real(amp)*real(amp)+imag(amp)*imag(amp) > 1e-10 {\n", - "\t\tfmt.Printf(\" |%02b> : %+.4f\\n\", i, real(amp))\n", - "\t}\n", - "}\n", - "\n", - "fmt.Println(\"\\nPrediction confirmed: CNOT|+0> = (|00> + |11>)/sqrt(2) = Bell state |Phi+>\")\n", - "\n", - "// Measure 1000 times to confirm only |00> and |11> appear\n", - "cBellM, _ := builder.New(\"bell-measure\", 2).H(0).CNOT(0, 1).MeasureAll().Build()\n", - "simM := statevector.New(2)\n", - "counts, _ := simM.Run(cBellM, 1000)\n", - "\n", - "fmt.Println(\"\\nMeasurement results (1000 shots):\")\n", - "for outcome, count := range counts {\n", - "\tfmt.Printf(\" |%s> : %d (%.1f%%)\\n\", outcome, count, float64(count)/10.0)\n", - "}\n", - "\n", - "svg := viz.Histogram(counts, viz.WithTitle(\"Bell State |Phi+> from CNOT|+0>\"))\n", - "gonbui.DisplayHTML(svg)" - ] - }, - { - "cell_type": "markdown", - "id": "y6vlbxdaw7k", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. Is CZ(0,1) the same as CZ(1,0)? Why or why not?\n", - "2. How many CNOT gates does a SWAP gate decompose into?\n", - "3. What state does CNOT|+0⟩ produce, and why is it significant?" - ] - }, - { - "cell_type": "markdown", - "id": "cell-21", - "metadata": {}, - "source": [ - "## Exercises\n", - "\n", - "### Exercise 1: 4-Qubit Controlled-RY\n", - "\n", - "Use `builder.Ctrl` to create a 3-controlled RY(pi/4) gate on a 4-qubit\n", - "circuit. Set all three control qubits to |1> and verify that the target\n", - "qubit receives the RY rotation." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "cell-22", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above and fill in the missing parts!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: 3-Controlled RY(pi/4) on 4 Qubits\n", - "//\n", - "// TODO: Build a 4-qubit circuit where:\n", - "// 1. Set all 3 control qubits (q0, q1, q2) to |1> using X gates\n", - "// 2. Apply a 3-controlled RY(pi/4) on the target qubit (q3)\n", - "// using builder.Ctrl(gate.RY(math.Pi/4), []int{0, 1, 2}, 3)\n", - "// 3. Inspect the statevector to verify the rotation was applied\n", - "//\n", - "// Hint: The target should have amplitudes cos(pi/8) and sin(pi/8)\n", - "\n", - "// cEx1, _ := builder.New(\"ctrl-ry\", 4).\n", - "// \t/* TODO: Set controls to |1> */\n", - "// \t/* TODO: Apply 3-controlled RY(pi/4) */\n", - "// \tBuild()\n", - "//\n", - "// fmt.Println(\"Exercise 1: 3-Controlled RY(pi/4) on 4 Qubits\")\n", - "// fmt.Println(\"===============================================\")\n", - "// fmt.Println(\"Circuit:\")\n", - "// fmt.Println(draw.String(cEx1))\n", - "//\n", - "// simEx1 := statevector.New(4)\n", - "// simEx1.Evolve(cEx1)\n", - "// svEx1 := simEx1.StateVector()\n", - "//\n", - "// fmt.Println(\"Statevector (non-zero amplitudes):\")\n", - "// for i, amp := range svEx1 {\n", - "// \tp := real(amp)*real(amp) + imag(amp)*imag(amp)\n", - "// \tif p > 1e-10 {\n", - "// \t\tfmt.Printf(\" |%04b> : %+.4f (prob=%.4f)\\n\", i, amp, p)\n", - "// \t}\n", - "// }\n", - "//\n", - "// fmt.Printf(\"\\nExpected target amplitudes: cos(pi/8)=%.4f, sin(pi/8)=%.4f\\n\",\n", - "// \tmath.Cos(math.Pi/8), math.Sin(math.Pi/8))\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above and fill in the missing parts!\")" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "cell-23", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above and fill in the missing parts!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: Custom Unitary Verification\n", - "//\n", - "// TODO: Create a custom phase-shift gate P(pi/3) = diag(1, e^{i*pi/3})\n", - "// and verify that it is unitary (U dagger U = I).\n", - "//\n", - "// Steps:\n", - "// 1. Define the 2x2 matrix: {1, 0, 0, cmplx.Exp(complex(0, phi))}\n", - "// 2. Use gate.Unitary(\"P(pi/3)\", matrix) to create the gate\n", - "// 3. Manually compute U dagger U and check it equals the identity\n", - "// 4. (Bonus) Try creating a non-unitary gate to see the error\n", - "//\n", - "// Hint: (U dagger)[i][k] = conj(U[k][i]) for the conjugate transpose\n", - "\n", - "// phi := math.Pi / 3\n", - "// customMatrix := []complex128{\n", - "// \t1, 0,\n", - "// \t0, cmplx.Exp(complex(0, phi)),\n", - "// }\n", - "//\n", - "// customGate, err := gate.Unitary(\"P(pi/3)\", customMatrix)\n", - "// if err != nil {\n", - "// \tfmt.Println(\"Error: matrix is not unitary!\", err)\n", - "// } else {\n", - "// \tfmt.Println(\"Gate created successfully (matrix IS unitary).\")\n", - "// \tfmt.Printf(\"Name: %s, Qubits: %d\\n\", customGate.Name(), customGate.Qubits())\n", - "// }\n", - "//\n", - "// // TODO: Manually verify U dagger U = I\n", - "// // m := customGate.Matrix()\n", - "// // dim := 2\n", - "// // for i := 0; i < dim; i++ {\n", - "// // for j := 0; j < dim; j++ {\n", - "// // var sum complex128\n", - "// // for k := 0; k < dim; k++ {\n", - "// // sum += cmplx.Conj(m[i*dim+k]) * m[j*dim+k]\n", - "// // }\n", - "// // fmt.Printf(\" (U dagger U)[%d][%d] = %+.6f\\n\", i, j, sum)\n", - "// // }\n", - "// // }\n", - "//\n", - "// // TODO: Try creating a non-unitary gate\n", - "// // badMatrix := []complex128{1, 1, 0, 1}\n", - "// // _, err = gate.Unitary(\"bad\", badMatrix)\n", - "// // fmt.Println(\"Rejected:\", err)\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above and fill in the missing parts!\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-24", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "\n", - "1. **CNOT** is the fundamental two-qubit gate: it flips the target when the\n", - " control is |1>. Together with single-qubit gates, it forms a universal\n", - " gate set.\n", - "\n", - "2. **CZ** is symmetric in its qubits -- swapping control and target gives the\n", - " same result. This makes it convenient for hardware with symmetric couplings.\n", - "\n", - "3. **SWAP** exchanges two qubit states and is equivalent to three CNOTs.\n", - " Transpilers insert SWAPs for qubit routing on real hardware.\n", - "\n", - "4. **Toffoli (CCX)** is a reversible AND gate: the target flips only when\n", - " both controls are |1>. Universal for reversible classical computation.\n", - "\n", - "5. **Fredkin (CSWAP)** is a controlled SWAP: the two targets swap only when\n", - " the control is |1>. Also universal for reversible computation.\n", - "\n", - "6. **Controlled-U** and **multi-controlled gates** generalize the control\n", - " pattern to arbitrary gates. Use `gate.Controlled(g, n)` or\n", - " `builder.Ctrl(g, controls, targets...)` in Goqu.\n", - "\n", - "7. **Custom unitary gates** can be created from raw matrices using\n", - " `gate.MustUnitary(name, matrix)`. Goqu automatically verifies unitarity.\n", - "\n", - "8. **Gate decomposition** is essential for real hardware: multi-qubit gates\n", - " must be broken into native 1Q and 2Q operations.\n", - "\n", - "9. **Superposition + controlled operation = entanglement**: applying CNOT to\n", - " |+0> creates the Bell state, the simplest entangled state." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/05-entanglement.ipynb b/notebooks/05-entanglement.ipynb deleted file mode 100644 index 4b58de9..0000000 --- a/notebooks/05-entanglement.ipynb +++ /dev/null @@ -1,977 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 05 - Multi-Qubit Systems and Entanglement\n", - "\n", - "**Prerequisites:** Notebooks 01-04. Familiarity with multi-qubit gates, especially CNOT.\n", - "\n", - "Entanglement is the defining quantum resource -- the phenomenon that separates\n", - "quantum computing from any classical model of computation. When two qubits are\n", - "entangled, measuring one instantly determines the outcome of the other,\n", - "regardless of the distance between them. This correlation is stronger than\n", - "anything achievable classically, and it underpins quantum teleportation, super-\n", - "dense coding, quantum error correction, and most quantum algorithms.\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Describe** entanglement and explain how it differs from classical correlation.\n", - "2. **Implement** all four Bell states and the GHZ state using Goqu.\n", - "3. **Predict** measurement correlations in entangled states.\n", - "4. **Explain** why entanglement does not enable faster-than-light communication.\n", - "\n", - "In this notebook we will:\n", - "\n", - "1. Understand **tensor products** and how multi-qubit states are represented.\n", - "2. Construct all four **Bell states** -- the simplest maximally entangled states.\n", - "3. Build **GHZ states** that extend entanglement to three or more qubits.\n", - "4. Use the **density matrix** formalism to quantify purity.\n", - "5. Address the common misconception that entanglement enables faster-than-light communication." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"fmt\"\n", - "\t\"math\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/circuit/ir\"\n", - "\t\"github.com/splch/goqu/sim/densitymatrix\"\n", - "\t\"github.com/splch/goqu/sim/statevector\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Multi-Qubit States and Tensor Products\n", - "\n", - "A single qubit lives in a 2-dimensional Hilbert space. When we have two qubits,\n", - "the combined system lives in the **tensor product** of the two individual spaces,\n", - "giving a 4-dimensional space with basis states |00>, |01>, |10>, |11>.\n", - "\n", - "A **product state** (also called a *separable* state) is one that can be written\n", - "as a tensor product of individual qubit states:\n", - "\n", - "$$|\\psi\\rangle = |\\psi_0\\rangle \\otimes |\\psi_1\\rangle$$\n", - "\n", - "An **entangled state** is any state that *cannot* be factored this way.\n", - "\n", - "Let's start by preparing a product state: apply H to qubit 0 while leaving\n", - "qubit 1 untouched. The result should be $(|0\\rangle + |1\\rangle)/\\sqrt{2} \\otimes |0\\rangle$.\n", - "In our bitstring convention $|q_1\\,q_0\\rangle$ (qubit 0 is the rightmost bit),\n", - "this is $(|00\\rangle + |01\\rangle)/\\sqrt{2}$." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Circuit:\n", - "Statevector (product state):\n", - " |00> : (0.7071+0.0000i)\n", - " |01> : (0.7071+0.0000i)\n", - "\n", - "This is a product state: (|0>+|1>)/sqrt(2) x |0> = (|00>+|01>)/sqrt(2)\n", - "Qubit 0 (rightmost bit) is in superposition; qubit 1 (leftmost bit) is |0>.\n", - "Each non-zero amplitude = 1/sqrt(2) = 0.7071\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Product state: H on qubit 0, nothing on qubit 1\n", - "cProduct, err := builder.New(\"product\", 2).\n", - "\tH(0).\n", - "\tBuild()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(cProduct))\n", - "\n", - "sim := statevector.New(2)\n", - "if err := sim.Evolve(cProduct); err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "sv := sim.StateVector()\n", - "fmt.Println(\"Statevector (product state):\")\n", - "for i, amp := range sv {\n", - "\tif real(amp)*real(amp)+imag(amp)*imag(amp) > 1e-10 {\n", - "\t\tfmt.Printf(\" |%02b> : %.4f\\n\", i, amp)\n", - "\t}\n", - "}\n", - "fmt.Println(\"\\nThis is a product state: (|0>+|1>)/sqrt(2) x |0> = (|00>+|01>)/sqrt(2)\")\n", - "fmt.Println(\"Qubit 0 (rightmost bit) is in superposition; qubit 1 (leftmost bit) is |0>.\")\n", - "fmt.Printf(\"Each non-zero amplitude = 1/sqrt(2) = %.4f\\n\", 1/math.Sqrt(2))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Bell States -- The Simplest Entangled States\n", - "\n", - "The four **Bell states** form a maximally entangled basis for two qubits.\n", - "They are created by combining a Hadamard gate with a CNOT gate:\n", - "\n", - "| Bell State | Circuit | Result |\n", - "|:---:|:---|:---|\n", - "| $|\\Phi^+\\rangle$ | H(0) then CNOT(0,1) on \\|00> | $(|00\\rangle + |11\\rangle)/\\sqrt{2}$ |\n", - "| $|\\Phi^-\\rangle$ | X(0), H(0), CNOT(0,1) on \\|00> | $(|00\\rangle - |11\\rangle)/\\sqrt{2}$ |\n", - "| $|\\Psi^+\\rangle$ | H(0), CNOT(0,1), X(1) on \\|00> | $(|01\\rangle + |10\\rangle)/\\sqrt{2}$ |\n", - "| $|\\Psi^-\\rangle$ | H(0), CNOT(0,1), X(1), Z(0) on \\|00> | $(|01\\rangle - |10\\rangle)/\\sqrt{2}$ |\n", - "\n", - "The key insight: after the CNOT, the two qubits are **correlated in every\n", - "measurement basis**, not just the computational basis. This is what\n", - "distinguishes entanglement from classical correlation." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== Bell State |Phi+> ===\n", - " |00> : +0.7071\n", - " |11> : +0.7071\n", - "\n", - "=== Bell State |Phi-> ===\n", - " |00> : +0.7071\n", - " |11> : -0.7071\n", - "\n", - "=== Bell State |Psi+> ===\n", - " |01> : +0.7071\n", - " |10> : +0.7071\n", - "\n", - "=== Bell State |Psi-> ===\n", - " |01> : -0.7071\n", - " |10> : +0.7071\n", - "\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "X\n", - "H\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "X\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "X\n", - "Z\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Build all four Bell states\n", - "bellCircuits := make(map[string]*ir.Circuit)\n", - "\n", - "// Phi+ = (|00> + |11>) / sqrt(2)\n", - "c, err := builder.New(\"Phi+\", 2).H(0).CNOT(0, 1).Build()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "bellCircuits[\"Phi+\"] = c\n", - "\n", - "// Phi- = (|00> - |11>) / sqrt(2)\n", - "c, err = builder.New(\"Phi-\", 2).X(0).H(0).CNOT(0, 1).Build()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "bellCircuits[\"Phi-\"] = c\n", - "\n", - "// Psi+ = (|01> + |10>) / sqrt(2)\n", - "c, err = builder.New(\"Psi+\", 2).H(0).CNOT(0, 1).X(1).Build()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "bellCircuits[\"Psi+\"] = c\n", - "\n", - "// Psi- = (|01> - |10>) / sqrt(2)\n", - "c, err = builder.New(\"Psi-\", 2).H(0).CNOT(0, 1).X(1).Z(0).Build()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "bellCircuits[\"Psi-\"] = c\n", - "\n", - "// Evolve and print each Bell state\n", - "bellNames := []string{\"Phi+\", \"Phi-\", \"Psi+\", \"Psi-\"}\n", - "for _, name := range bellNames {\n", - "\tbc := bellCircuits[name]\n", - "\tfmt.Printf(\"=== Bell State |%s> ===\\n\", name)\n", - "\tgonbui.DisplayHTML(draw.SVG(bc))\n", - "\n", - "\tsim := statevector.New(2)\n", - "\tif err := sim.Evolve(bc); err != nil {\n", - "\t\tpanic(err)\n", - "\t}\n", - "\n", - "\tsv := sim.StateVector()\n", - "\tfor i, amp := range sv {\n", - "\t\tif real(amp)*real(amp)+imag(amp)*imag(amp) > 1e-10 {\n", - "\t\t\tfmt.Printf(\" |%02b> : %+.4f\\n\", i, real(amp))\n", - "\t\t}\n", - "\t}\n", - "\tfmt.Println()\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Perfect Correlation in Measurements\n", - "\n", - "Let's measure the Bell state $|\\Phi^+\\rangle$ many times. Because the qubits\n", - "are maximally entangled, we should see only two outcomes: **|00>** and **|11>**,\n", - "each with roughly 50% probability. The outcomes |01> and |10> should\n", - "never appear -- the qubits are perfectly correlated." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Measurement results (1000 shots):\n", - " |00> : 498 (49.8%)\n", - " |11> : 502 (50.2%)\n", - "\n", - "Only |00> and |11> appear -- perfect correlation!\n" - ] - } - ], - "source": [ - "%%\n", - "// Measure Bell Phi+ state 1000 times\n", - "cBellMeasure, err := builder.New(\"Phi+ measure\", 2).\n", - "\tH(0).\n", - "\tCNOT(0, 1).\n", - "\tMeasureAll().\n", - "\tBuild()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "sim := statevector.New(2)\n", - "counts, err := sim.Run(cBellMeasure, 1000)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Measurement results (1000 shots):\")\n", - "for outcome, count := range counts {\n", - "\tfmt.Printf(\" |%s> : %d (%.1f%%)\\n\", outcome, count, float64(count)/10.0)\n", - "}\n", - "fmt.Println(\"\\nOnly |00> and |11> appear -- perfect correlation!\")" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "Bell State |Phi+> Measurements\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "100\n", - "\n", - "200\n", - "\n", - "300\n", - "\n", - "400\n", - "\n", - "500\n", - "\n", - "600\n", - "\n", - "00\n", - "\n", - "11\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Visualize the measurement histogram\n", - "cBellHist, _ := builder.New(\"Phi+\", 2).\n", - "\tH(0).CNOT(0, 1).MeasureAll().Build()\n", - "\n", - "simHist := statevector.New(2)\n", - "countsHist, _ := simHist.Run(cBellHist, 1000)\n", - "\n", - "svg := viz.Histogram(countsHist, viz.WithTitle(\"Bell State |Phi+> Measurements\"))\n", - "gonbui.DisplayHTML(svg)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## GHZ State: Entanglement Beyond Two Qubits\n", - "\n", - "The **Greenberger-Horne-Zeilinger (GHZ) state** extends Bell-style entanglement\n", - "to three or more qubits:\n", - "\n", - "$$|\\text{GHZ}\\rangle = \\frac{|000\\rangle + |111\\rangle}{\\sqrt{2}}$$\n", - "\n", - "The construction is straightforward: apply H to qubit 0, then cascade CNOT\n", - "gates to entangle each subsequent qubit. Like the Bell state, measurement\n", - "produces only \"all zeros\" or \"all ones\" outcomes." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "3-qubit GHZ circuit:\n", - "Measurement results (1000 shots):\n", - " |111> : 511\n", - " |000> : 489\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "M\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "3-Qubit GHZ State Measurements\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "100\n", - "\n", - "200\n", - "\n", - "300\n", - "\n", - "400\n", - "\n", - "500\n", - "\n", - "600\n", - "\n", - "000\n", - "\n", - "111\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Build a 3-qubit GHZ state: H(0), CNOT(0,1), CNOT(0,2)\n", - "cGHZ, err := builder.New(\"GHZ-3\", 3).\n", - "\tH(0).\n", - "\tCNOT(0, 1).\n", - "\tCNOT(0, 2).\n", - "\tMeasureAll().\n", - "\tBuild()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"3-qubit GHZ circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(cGHZ))\n", - "\n", - "simGHZ := statevector.New(3)\n", - "countsGHZ, err := simGHZ.Run(cGHZ, 1000)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Measurement results (1000 shots):\")\n", - "for outcome, count := range countsGHZ {\n", - "\tfmt.Printf(\" |%s> : %d\\n\", outcome, count)\n", - "}\n", - "\n", - "svgGHZ := viz.Histogram(countsGHZ, viz.WithTitle(\"3-Qubit GHZ State Measurements\"))\n", - "gonbui.DisplayHTML(svgGHZ)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Density Matrix and Purity\n", - "\n", - "The **density matrix** $\\rho$ is a more general representation of quantum states\n", - "that can describe both pure and mixed states. For a pure state $|\\psi\\rangle$:\n", - "\n", - "$$\\rho = |\\psi\\rangle\\langle\\psi|$$\n", - "\n", - "The **purity** of a state is defined as $\\text{Tr}(\\rho^2)$:\n", - "\n", - "- **Pure states** have purity = 1 (this includes entangled states!)\n", - "- **Mixed states** have purity < 1\n", - "- A **maximally mixed** state of dimension $d$ has purity = $1/d$\n", - "\n", - "An important subtlety: a Bell state like $|\\Phi^+\\rangle$ is a **pure** state\n", - "(purity = 1) even though it is maximally entangled. Entanglement and mixedness\n", - "are different concepts. The mixedness (impurity) only appears when you trace\n", - "out one subsystem to look at the reduced density matrix of a single qubit." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Purity of Bell state |Phi+> : 1.0000\n", - "Is pure state (purity ~ 1.0): true\n", - "\n", - "Density matrix (non-zero elements):\n", - " rho[|00>,|00>] = +0.5000\n", - " rho[|00>,|11>] = +0.5000\n", - " rho[|11>,|00>] = +0.5000\n", - " rho[|11>,|11>] = +0.5000\n" - ] - } - ], - "source": [ - "%%\n", - "// Density matrix for the Bell state Phi+\n", - "cBellDM, err := builder.New(\"Phi+\", 2).\n", - "\tH(0).\n", - "\tCNOT(0, 1).\n", - "\tBuild()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "dm := densitymatrix.New(2)\n", - "if err := dm.Evolve(cBellDM); err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "purity := dm.Purity()\n", - "fmt.Printf(\"Purity of Bell state |Phi+> : %.4f\\n\", purity)\n", - "fmt.Printf(\"Is pure state (purity ~ 1.0): %v\\n\", math.Abs(purity-1.0) < 1e-10)\n", - "fmt.Println(\"\\nDensity matrix (non-zero elements):\")\n", - "\n", - "rho := dm.DensityMatrix()\n", - "dim := 4 // 2^2 qubits\n", - "for r := 0; r < dim; r++ {\n", - "\tfor c := 0; c < dim; c++ {\n", - "\t\tv := rho[r*dim+c]\n", - "\t\tif real(v)*real(v)+imag(v)*imag(v) > 1e-10 {\n", - "\t\t\tfmt.Printf(\" rho[|%02b>,|%02b>] = %+.4f\\n\", r, c, real(v))\n", - "\t\t}\n", - "\t}\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "Density Matrix of |Phi+>\n", - "Re(ρ)\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "|00⟩\n", - "|00⟩\n", - "|01⟩\n", - "|01⟩\n", - "|10⟩\n", - "|10⟩\n", - "|11⟩\n", - "|11⟩\n", - "Im(ρ)\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "|00⟩\n", - "|00⟩\n", - "|01⟩\n", - "|01⟩\n", - "|10⟩\n", - "|10⟩\n", - "|11⟩\n", - "|11⟩\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Visualize the density matrix with a state city plot\n", - "cBellCity, _ := builder.New(\"Phi+\", 2).\n", - "\tH(0).CNOT(0, 1).Build()\n", - "\n", - "dmCity := densitymatrix.New(2)\n", - "dmCity.Evolve(cBellCity)\n", - "\n", - "rhoCity := dmCity.DensityMatrix()\n", - "svgCity := viz.StateCity(rhoCity, 4, viz.WithTitle(\"Density Matrix of |Phi+>\"))\n", - "gonbui.DisplayHTML(svgCity)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** If we measure qubit 0 of the Bell state $|\\Phi^+\\rangle = (|00\\rangle + |11\\rangle)/\\sqrt{2}$ and get the result **|0>**, what do we know about qubit 1?\n", - "\n", - "**Pause and predict** before running the next cell.\n", - "\n", - "*Hint: Look at which basis states appear in the Bell state and what they imply about the relationship between the two qubits.*" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Total shots: 10000\n", - "Qubits agreed: 10000\n", - "Agreement rate: 100.0%\n", - "\n", - "Prediction confirmed: measuring qubit 0 completely determines qubit 1.\n" - ] - } - ], - "source": [ - "%%\n", - "// Verify: measuring qubit 0 of Phi+ determines qubit 1\n", - "cVerify, _ := builder.New(\"Phi+ verify\", 2).\n", - "\tH(0).CNOT(0, 1).MeasureAll().Build()\n", - "\n", - "simVerify := statevector.New(2)\n", - "countsVerify, _ := simVerify.Run(cVerify, 10000)\n", - "\n", - "// Check that qubits always agree\n", - "totalShots := 0\n", - "agreedShots := 0\n", - "for outcome, count := range countsVerify {\n", - "\ttotalShots += count\n", - "\t// In the bitstring, both bits should be the same\n", - "\tif outcome[0] == outcome[1] {\n", - "\t\tagreedShots += count\n", - "\t}\n", - "}\n", - "\n", - "fmt.Printf(\"Total shots: %d\\n\", totalShots)\n", - "fmt.Printf(\"Qubits agreed: %d\\n\", agreedShots)\n", - "fmt.Printf(\"Agreement rate: %.1f%%\\n\", float64(agreedShots)/float64(totalShots)*100)\n", - "fmt.Println(\"\\nPrediction confirmed: measuring qubit 0 completely determines qubit 1.\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Misconception: Entanglement Enables Faster-Than-Light Communication\n", - "\n", - "A common misconception is that entanglement could be used to send information\n", - "instantaneously across any distance. The reasoning goes: \"If Alice measures her\n", - "qubit and it instantly determines Bob's qubit, can't Alice use that to send a\n", - "message to Bob?\"\n", - "\n", - "**The answer is no.** This is guaranteed by the **no-communication theorem**.\n", - "Here is why:\n", - "\n", - "1. **Alice cannot choose her measurement outcome.** When she measures her half\n", - " of a Bell pair, she gets |0> or |1> with equal probability. She cannot force\n", - " a particular result.\n", - "\n", - "2. **Bob's local statistics are unchanged.** Before he learns Alice's result,\n", - " Bob's qubit looks like a maximally mixed state -- completely random.\n", - " No matter what Alice does (or doesn't do) to her qubit, Bob sees 50/50\n", - " outcomes. He cannot tell whether Alice has measured or not.\n", - "\n", - "3. **Correlation requires classical communication.** The correlations only become\n", - " visible when Alice and Bob **compare their results**, which requires a\n", - " classical communication channel limited by the speed of light.\n", - "\n", - "Entanglement is a resource for protocols like quantum teleportation and\n", - "superdense coding, but these protocols always require a classical side-channel.\n", - "Quantum mechanics is non-local in its correlations, but **no information\n", - "travels faster than light**." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. Can you distinguish |00⟩+|11⟩ from |00⟩-|11⟩ using only Z-basis measurements? Why or why not?\n", - "2. What measurement correlations prove entanglement versus classical correlation?\n", - "3. True or false: An entangled state is always a mixed state. Why?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Exercises\n", - "\n", - "### Exercise 1: 3-Qubit GHZ State Verification\n", - "\n", - "Create a 3-qubit GHZ state using H(0), CNOT(0,1), CNOT(0,2).\n", - "Verify with 2000 measurement shots that only |000> and |111> appear." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the gates!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: Build and verify a 3-qubit GHZ state\n", - "// GHZ = (|000> + |111>) / sqrt(2)\n", - "//\n", - "// TODO: Apply H to qubit 0, then cascade CNOT gates to entangle qubits 1 and 2\n", - "// cEx1, _ := builder.New(\"GHZ-3-ex\", 3).\n", - "// \t/* your gates here */\n", - "// \tMeasureAll().\n", - "// \tBuild()\n", - "//\n", - "// fmt.Println(\"GHZ-3 circuit:\")\n", - "// fmt.Println(draw.String(cEx1))\n", - "//\n", - "// simEx1 := statevector.New(3)\n", - "// countsEx1, _ := simEx1.Run(cEx1, 2000)\n", - "//\n", - "// fmt.Println(\"Results (2000 shots):\")\n", - "// allValid := true\n", - "// for outcome, count := range countsEx1 {\n", - "// \tfmt.Printf(\" |%s> : %d\\n\", outcome, count)\n", - "// \tif outcome != \"000\" && outcome != \"111\" {\n", - "// \t\tallValid = false\n", - "// \t}\n", - "// }\n", - "// fmt.Printf(\"\\nOnly |000> and |111> observed: %v\\n\", allValid)\n", - "//\n", - "// gonbui.DisplayHTML(viz.Histogram(countsEx1, viz.WithTitle(\"Exercise 1: GHZ-3 Measurements\")))\n", - "fmt.Println(\"Uncomment the code above and fill in the gates!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Exercise 2: Compose Two Circuits Using `ir.Tensor`\n", - "\n", - "The `ir.Tensor` function places two circuits on **disjoint qubit spaces**.\n", - "If circuit A has $n$ qubits and circuit B has $m$ qubits, the result has\n", - "$n + m$ qubits. B's qubit indices are shifted by $n$.\n", - "\n", - "Build two single-qubit circuits (H on qubit 0, and X on qubit 0),\n", - "then tensor them together to create a 2-qubit circuit that applies H to\n", - "qubit 0 and X to qubit 1." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the gates!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: Use ir.Tensor to compose two independent circuits\n", - "// ir.Tensor places circuit A on qubits 0..n-1 and circuit B on qubits n..n+m-1\n", - "//\n", - "// TODO: Build two single-qubit circuits (H and X), then tensor them together\n", - "// cA, _ := builder.New(\"H-circuit\", 1). /* gate */ .Build()\n", - "// cB, _ := builder.New(\"X-circuit\", 1). /* gate */ .Build()\n", - "//\n", - "// // Tensor product: A on qubit 0, B on qubit 1\n", - "// cTensor := ir.Tensor(cA, cB)\n", - "//\n", - "// fmt.Println(\"Tensored circuit (H on q0, X on q1):\")\n", - "// fmt.Println(draw.String(cTensor))\n", - "//\n", - "// simTensor := statevector.New(2)\n", - "// simTensor.Evolve(cTensor)\n", - "//\n", - "// svTensor := simTensor.StateVector()\n", - "// fmt.Println(\"Statevector:\")\n", - "// for i, amp := range svTensor {\n", - "// \tp := real(amp)*real(amp) + imag(amp)*imag(amp)\n", - "// \tif p > 1e-10 {\n", - "// \t\tfmt.Printf(\" |%02b> : %.4f (prob = %.4f)\\n\", i, amp, p)\n", - "// \t}\n", - "// }\n", - "// fmt.Println(\"\\nExpected: (|0>+|1>)/sqrt(2) tensor |1> = (|01>+|11>)/sqrt(2)\")\n", - "// fmt.Println(\"This is a product state -- each qubit is independent.\")\n", - "fmt.Println(\"Uncomment the code above and fill in the gates!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "\n", - "1. **Tensor products** describe multi-qubit systems. An $n$-qubit state lives\n", - " in a $2^n$-dimensional Hilbert space.\n", - "\n", - "2. **Entangled states** cannot be factored into a tensor product of individual\n", - " qubit states. This is fundamentally different from classical correlation.\n", - "\n", - "3. The **Bell states** are the four maximally entangled 2-qubit states.\n", - " They are built with H + CNOT and form a complete orthonormal basis.\n", - "\n", - "4. **GHZ states** extend entanglement to $n$ qubits: $\\frac{|00\\ldots0\\rangle + |11\\ldots1\\rangle}{\\sqrt{2}}$.\n", - "\n", - "5. The **density matrix** formalism generalizes state descriptions. **Purity**\n", - " $\\text{Tr}(\\rho^2)$ distinguishes pure states (= 1) from mixed states (< 1).\n", - " Entangled states can be pure!\n", - "\n", - "6. **Entanglement does not enable faster-than-light communication.** The\n", - " no-communication theorem guarantees that local measurements on one half of\n", - " an entangled pair reveal no information about what was done to the other half." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/notebooks/06-teleportation.ipynb b/notebooks/06-teleportation.ipynb deleted file mode 100644 index 06bf0bb..0000000 --- a/notebooks/06-teleportation.ipynb +++ /dev/null @@ -1,914 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "cell-0", - "metadata": {}, - "source": [ - "# 06 - Quantum Protocols: Teleportation and Superdense Coding\n", - "\n", - "**Prerequisites:** Notebooks 01-05. Familiarity with entanglement and measurement.\n", - "\n", - "Quantum teleportation and superdense coding are two of the most celebrated\n", - "protocols in quantum information. They reveal the power of **entanglement as a\n", - "resource** -- something that has no classical analogue.\n", - "\n", - "- **Quantum teleportation** transmits an unknown quantum state from Alice to Bob\n", - " using one shared Bell pair and two classical bits. No physical qubit travels\n", - " between them; the quantum information is reconstructed at the destination.\n", - "\n", - "- **Superdense coding** does the reverse: Alice sends two classical bits of\n", - " information to Bob by transmitting only one qubit, again leveraging a\n", - " pre-shared Bell pair.\n", - "\n", - "Both protocols require **dynamic circuits** -- mid-circuit measurement followed\n", - "by classically conditioned gates (feed-forward).\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Implement** the quantum teleportation protocol with mid-circuit measurement.\n", - "2. **Implement** superdense coding to send 2 classical bits via 1 qubit.\n", - "3. **Explain** the role of classical communication in both protocols.\n", - "4. **Verify** that teleportation preserves the input state's measurement statistics.\n", - "\n", - "In this notebook we will:\n", - "\n", - "1. Build the full quantum teleportation circuit and verify it works.\n", - "2. Understand the role of classical communication.\n", - "3. Implement superdense coding for all four 2-bit messages.\n", - "4. Use Goqu's `If`, `Measure`, and `RunDynamic` for feed-forward control." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "cell-1", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"fmt\"\n", - "\t\"math\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/circuit/gate\"\n", - "\t\"github.com/splch/goqu/sim/statevector\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-2", - "metadata": {}, - "source": [ - "## The Quantum Teleportation Protocol\n", - "\n", - "Suppose Alice has a qubit in an unknown state $|\\psi\\rangle = \\alpha|0\\rangle + \\beta|1\\rangle$ and\n", - "wants to transmit it to Bob. She cannot simply measure it (that would destroy the\n", - "superposition), and the no-cloning theorem says she cannot copy it. But with a\n", - "shared Bell pair and two classical bits, she can teleport the state perfectly.\n", - "\n", - "### The protocol step by step\n", - "\n", - "**Setup:** Alice and Bob share a Bell pair $|\\Phi^+\\rangle = \\frac{1}{\\sqrt{2}}(|00\\rangle + |11\\rangle)$.\n", - "Alice holds qubit 1 (her half) and Bob holds qubit 2.\n", - "\n", - "**Step 1 -- Alice's operations:**\n", - "- Alice applies CNOT with her data qubit (q0) as control and her Bell qubit (q1) as target.\n", - "- Alice applies H to her data qubit (q0).\n", - "\n", - "**Step 2 -- Alice measures:**\n", - "- Alice measures both her qubits (q0 and q1), obtaining two classical bits.\n", - "\n", - "**Step 3 -- Bob's corrections (classically conditioned):**\n", - "- If Alice's measurement of q1 is 1, Bob applies X to his qubit (q2).\n", - "- If Alice's measurement of q0 is 1, Bob applies Z to his qubit (q2).\n", - "\n", - "After these corrections, Bob's qubit is in exactly the state $|\\psi\\rangle$.\n", - "\n", - "The circuit uses 3 qubits and 2 classical bits:\n", - "- q0: Alice's data qubit (the state to teleport)\n", - "- q1: Alice's half of the Bell pair\n", - "- q2: Bob's half of the Bell pair\n", - "- c0: result of measuring q0\n", - "- c1: result of measuring q1" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "cell-3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Quantum Teleportation Circuit:\n", - "Is dynamic circuit: true\n", - "\n", - "Input state: RY(pi/3)|0> = 0.8660|0> + 0.5000|1>\n", - "Expected probabilities: P(0) = 0.7500, P(1) = 0.2500\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "RY(pi/3)\n", - "H\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "H\n", - "M\n", - "M\n", - "X\n", - "Z\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Build the full teleportation circuit.\n", - "// We prepare an interesting state on q0 using RY(pi/3),\n", - "// which gives |psi> = cos(pi/6)|0> + sin(pi/6)|1> = (sqrt(3)/2)|0> + (1/2)|1>.\n", - "\n", - "cTeleport, err := builder.New(\"teleport\", 3).WithClbits(2).\n", - "\t// Prepare the state to teleport on q0\n", - "\tRY(math.Pi/3, 0).\n", - "\t// Create Bell pair between q1 (Alice) and q2 (Bob)\n", - "\tH(1).CNOT(1, 2).\n", - "\t// Alice's operations: entangle q0 with q1\n", - "\tCNOT(0, 1).H(0).\n", - "\t// Alice measures both her qubits\n", - "\tMeasure(0, 0). // q0 -> c0\n", - "\tMeasure(1, 1). // q1 -> c1\n", - "\t// Bob's corrections conditioned on Alice's results\n", - "\tIf(1, 1, gate.X, 2). // if c1 == 1, apply X to q2\n", - "\tIf(0, 1, gate.Z, 2). // if c0 == 1, apply Z to q2\n", - "\tBuild()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Quantum Teleportation Circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(cTeleport))\n", - "fmt.Printf(\"Is dynamic circuit: %v\\n\", cTeleport.IsDynamic())\n", - "\n", - "// Show the input state we are teleporting\n", - "angle := math.Pi / 3\n", - "fmt.Printf(\"\\nInput state: RY(pi/3)|0> = %.4f|0> + %.4f|1>\\n\",\n", - "\tmath.Cos(angle/2), math.Sin(angle/2))\n", - "fmt.Printf(\"Expected probabilities: P(0) = %.4f, P(1) = %.4f\\n\",\n", - "\tmath.Cos(angle/2)*math.Cos(angle/2),\n", - "\tmath.Sin(angle/2)*math.Sin(angle/2))" - ] - }, - { - "cell_type": "markdown", - "id": "cell-4", - "metadata": {}, - "source": [ - "## Verifying Teleportation Works\n", - "\n", - "To verify that teleportation succeeded, we add a final measurement on Bob's\n", - "qubit (q2) and check that the measurement statistics match the original input\n", - "state.\n", - "\n", - "The input state $\\text{RY}(\\pi/3)|0\\rangle$ has:\n", - "- $P(0) = \\cos^2(\\pi/6) = 3/4 = 0.75$\n", - "- $P(1) = \\sin^2(\\pi/6) = 1/4 = 0.25$\n", - "\n", - "If teleportation works correctly, Bob's qubit should show these same\n", - "probabilities regardless of Alice's measurement outcomes." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "cell-5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Teleportation verification (4000 shots):\n", - "\n", - "All outcomes [c2 c1 c0]:\n", - " 010: 768\n", - " 101: 259\n", - " 111: 234\n", - " 001: 780\n", - " 110: 235\n", - " 011: 689\n", - " 000: 792\n", - " 100: 243\n", - "\n", - "Bob's qubit (q2) statistics:\n", - " P(0) = 3029/4000 = 0.7572 (expected: 0.7500)\n", - " P(1) = 971/4000 = 0.2427 (expected: 0.2500)\n", - "\n", - "Teleportation successful: Bob's statistics match the input state!\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "Bob's Qubit After Teleportation\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "500\n", - "\n", - "1000\n", - "\n", - "1500\n", - "\n", - "2000\n", - "\n", - "2500\n", - "\n", - "3000\n", - "\n", - "3500\n", - "\n", - "0\n", - "\n", - "1\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Build teleportation circuit with a final measurement on Bob's qubit.\n", - "// We use 3 classical bits: c0 and c1 for Alice, c2 for Bob's result.\n", - "cVerify, err := builder.New(\"teleport-verify\", 3).WithClbits(3).\n", - "\t// Prepare state to teleport\n", - "\tRY(math.Pi/3, 0).\n", - "\t// Bell pair\n", - "\tH(1).CNOT(1, 2).\n", - "\t// Alice's operations\n", - "\tCNOT(0, 1).H(0).\n", - "\t// Alice measures\n", - "\tMeasure(0, 0).Measure(1, 1).\n", - "\t// Bob corrects\n", - "\tIf(1, 1, gate.X, 2).\n", - "\tIf(0, 1, gate.Z, 2).\n", - "\t// Bob measures his qubit\n", - "\tMeasure(2, 2).\n", - "\tBuild()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "sim := statevector.New(3)\n", - "counts, err := sim.RunDynamic(cVerify, 4000)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "// Extract Bob's qubit statistics (c2 is the most-significant bit in the bitstring)\n", - "bob0, bob1 := 0, 0\n", - "for bs, n := range counts {\n", - "\t// Bitstring format is [c2 c1 c0] -- c2 is the leftmost character\n", - "\tif bs[0] == '0' {\n", - "\t\tbob0 += n\n", - "\t} else {\n", - "\t\tbob1 += n\n", - "\t}\n", - "}\n", - "\n", - "total := bob0 + bob1\n", - "fmt.Println(\"Teleportation verification (4000 shots):\")\n", - "fmt.Println(\"\\nAll outcomes [c2 c1 c0]:\")\n", - "for bs, n := range counts {\n", - "\tfmt.Printf(\" %s: %d\\n\", bs, n)\n", - "}\n", - "fmt.Println(\"\\nBob's qubit (q2) statistics:\")\n", - "fmt.Printf(\" P(0) = %d/%d = %.4f (expected: 0.7500)\\n\", bob0, total, float64(bob0)/float64(total))\n", - "fmt.Printf(\" P(1) = %d/%d = %.4f (expected: 0.2500)\\n\", bob1, total, float64(bob1)/float64(total))\n", - "fmt.Println(\"\\nTeleportation successful: Bob's statistics match the input state!\")\n", - "\n", - "// Visualize Bob's measurement results\n", - "bobCounts := map[string]int{\"0\": bob0, \"1\": bob1}\n", - "gonbui.DisplayHTML(viz.Histogram(bobCounts, viz.WithTitle(\"Bob's Qubit After Teleportation\")))" - ] - }, - { - "cell_type": "markdown", - "id": "cell-7", - "metadata": {}, - "source": [ - "## Superdense Coding\n", - "\n", - "Superdense coding is the \"dual\" protocol to teleportation. While teleportation\n", - "sends one qubit of quantum information using two classical bits and one shared\n", - "ebit, superdense coding sends **two classical bits** using one qubit and one\n", - "shared ebit.\n", - "\n", - "### The protocol\n", - "\n", - "**Setup:** Alice and Bob share a Bell pair $|\\Phi^+\\rangle$. Alice holds qubit 0, Bob holds qubit 1.\n", - "\n", - "**Encoding:** To send a 2-bit message, Alice applies an operation to *only her qubit*:\n", - "\n", - "| Message | Alice applies | Resulting Bell state |\n", - "|:---:|:---:|:---:|\n", - "| 00 | I (nothing) | $|\\Phi^+\\rangle = \\frac{1}{\\sqrt{2}}(|00\\rangle + |11\\rangle)$ |\n", - "| 01 | Z | $|\\Phi^-\\rangle = \\frac{1}{\\sqrt{2}}(|00\\rangle - |11\\rangle)$ |\n", - "| 10 | X | $|\\Psi^+\\rangle = \\frac{1}{\\sqrt{2}}(|01\\rangle + |10\\rangle)$ |\n", - "| 11 | X then Z | $|\\Psi^-\\rangle = \\frac{1}{\\sqrt{2}}(|01\\rangle - |10\\rangle)$ |\n", - "\n", - "**Decoding:** Alice sends her qubit to Bob. Bob now has both halves of the\n", - "(modified) Bell pair. He applies CNOT(0,1) then H(0) and measures both qubits\n", - "to recover the original 2-bit message.\n", - "\n", - "This works because the four Bell states are orthogonal -- Bob can perfectly\n", - "distinguish them with the right measurement." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "cell-8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== Message: 00 (Alice applies I (identity)) ===\n", - "Bob decodes: map[00:1000]\n", - "\n", - "=== Message: 01 (Alice applies Z) ===\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "H\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "Z\n", - "\n", - "\n", - "\n", - "H\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Bob decodes: map[01:1000]\n", - "\n", - "=== Message: 10 (Alice applies X) ===\n", - "Bob decodes: map[10:1000]\n", - "\n", - "=== Message: 11 (Alice applies X then Z) ===\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "X\n", - "\n", - "\n", - "\n", - "H\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "X\n", - "Z\n", - "\n", - "\n", - "\n", - "H\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Bob decodes: map[11:1000]\n", - "\n" - ] - } - ], - "source": [ - "%%\n", - "// Demonstrate superdense coding for all four 2-bit messages.\n", - "\n", - "messages := []struct {\n", - "\tbits string\n", - "\tdesc string\n", - "}{\n", - "\t{\"00\", \"I (identity)\"},\n", - "\t{\"01\", \"Z\"},\n", - "\t{\"10\", \"X\"},\n", - "\t{\"11\", \"X then Z\"},\n", - "}\n", - "\n", - "for _, msg := range messages {\n", - "\t// Build the superdense coding circuit\n", - "\tb := builder.New(\"superdense-\"+msg.bits, 2)\n", - "\n", - "\t// Step 1: Create shared Bell pair\n", - "\tb.H(0).CNOT(0, 1)\n", - "\n", - "\t// Step 2: Alice encodes her 2-bit message on her qubit (q0)\n", - "\tswitch msg.bits {\n", - "\tcase \"00\":\n", - "\t\t// Apply nothing (identity)\n", - "\tcase \"01\":\n", - "\t\tb.Z(0)\n", - "\tcase \"10\":\n", - "\t\tb.X(0)\n", - "\tcase \"11\":\n", - "\t\tb.X(0).Z(0)\n", - "\t}\n", - "\n", - "\t// Step 3: Bob decodes -- CNOT then H, then measure\n", - "\tb.CNOT(0, 1).H(0).MeasureAll()\n", - "\n", - "\tc, err := b.Build()\n", - "\tif err != nil {\n", - "\t\tpanic(err)\n", - "\t}\n", - "\n", - "\tfmt.Printf(\"=== Message: %s (Alice applies %s) ===\\n\", msg.bits, msg.desc)\n", - "\tgonbui.DisplayHTML(draw.SVG(c))\n", - "\n", - "\tsim := statevector.New(2)\n", - "\tcounts, err := sim.Run(c, 1000)\n", - "\tif err != nil {\n", - "\t\tpanic(err)\n", - "\t}\n", - "\n", - "\tfmt.Printf(\"Bob decodes: %v\\n\\n\", counts)\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "cell-9", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** In the superdense coding protocol, what if Alice wants to send the\n", - "message **11**? She applies X then Z to her qubit. What will Bob measure?\n", - "\n", - "**Pause and predict** before reading further.\n", - "\n", - "*Hint: After X then Z on Alice's qubit, the Bell pair becomes $|\\Psi^-\\rangle$. Bob applies CNOT(0,1) then H(0) to decode.*" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "cell-10", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Superdense coding: Alice sends message 11\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "X\n", - "Z\n", - "\n", - "\n", - "\n", - "H\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Bob's measurement: map[11:1000]\n", - "\n", - "Prediction confirmed: Bob always measures 11!\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "Superdense Coding: Message 11\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "200\n", - "\n", - "400\n", - "\n", - "600\n", - "\n", - "800\n", - "\n", - "1000\n", - "\n", - "11\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Verify the prediction: superdense coding with message 11\n", - "cSD11, err := builder.New(\"superdense-11\", 2).\n", - "\t// Bell pair\n", - "\tH(0).CNOT(0, 1).\n", - "\t// Alice encodes 11: X then Z\n", - "\tX(0).Z(0).\n", - "\t// Bob decodes\n", - "\tCNOT(0, 1).H(0).\n", - "\tMeasureAll().\n", - "\tBuild()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Superdense coding: Alice sends message 11\")\n", - "gonbui.DisplayHTML(draw.SVG(cSD11))\n", - "\n", - "sim := statevector.New(2)\n", - "counts, err := sim.Run(cSD11, 1000)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"Bob's measurement: %v\\n\", counts)\n", - "if counts[\"11\"] == 1000 {\n", - "\tfmt.Println(\"\\nPrediction confirmed: Bob always measures 11!\")\n", - "}\n", - "\n", - "gonbui.DisplayHTML(viz.Histogram(counts, viz.WithTitle(\"Superdense Coding: Message 11\")))" - ] - }, - { - "cell_type": "markdown", - "id": "3dfe5f1ukaj", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. In teleportation, does any quantum information travel through the classical channel? Why or why not?\n", - "2. How many classical bits does superdense coding send per shared Bell pair?\n", - "3. Why do both teleportation and superdense coding require classical communication in addition to entanglement?" - ] - }, - { - "cell_type": "markdown", - "id": "cell-11", - "metadata": {}, - "source": [ - "## Exercises\n", - "\n", - "### Exercise 1: Teleport an Arbitrary State\n", - "\n", - "Modify the teleportation circuit to teleport a different state. Use\n", - "`RY(theta, 0)` with a different angle (e.g., $\\pi/4$ or $2\\pi/3$) and verify\n", - "that Bob's measurement statistics match the expected probabilities of the\n", - "input state.\n", - "\n", - "*Hint:* For $\\text{RY}(\\theta)|0\\rangle$, the probabilities are\n", - "$P(0) = \\cos^2(\\theta/2)$ and $P(1) = \\sin^2(\\theta/2)$." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "cell-12", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above and fill in the missing parts!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: Teleport RY(2*pi/3)|0>\n", - "// Expected: P(0) = cos^2(pi/3) = 0.25, P(1) = sin^2(pi/3) = 0.75\n", - "//\n", - "// TODO: Choose a different angle for RY and build the teleportation circuit.\n", - "// Follow the same pattern as the example above:\n", - "// 1. Prepare the state with RY(theta, 0)\n", - "// 2. Create a Bell pair on qubits 1 and 2\n", - "// 3. Alice's operations: CNOT(0,1), H(0)\n", - "// 4. Alice measures q0 -> c0, q1 -> c1\n", - "// 5. Bob corrects: If c1==1 apply X to q2, If c0==1 apply Z to q2\n", - "// 6. Bob measures q2 -> c2\n", - "\n", - "theta := 2.0 * math.Pi / 3.0\n", - "\n", - "// cEx1, err := builder.New(\"teleport-ex1\", 3).WithClbits(3).\n", - "// \tRY(theta, 0). // Prepare state to teleport\n", - "// \t/* TODO: Bell pair */\n", - "// \t/* TODO: Alice's operations */\n", - "// \t/* TODO: Alice measures */\n", - "// \t/* TODO: Bob corrects */\n", - "// \tMeasure(2, 2). // Bob measures\n", - "// \tBuild()\n", - "// if err != nil {\n", - "// \tpanic(err)\n", - "// }\n", - "//\n", - "// fmt.Println(draw.String(cEx1))\n", - "//\n", - "// simEx1 := statevector.New(3)\n", - "// countsEx1, err := simEx1.RunDynamic(cEx1, 4000)\n", - "// if err != nil {\n", - "// \tpanic(err)\n", - "// }\n", - "//\n", - "// // TODO: Extract Bob's statistics from countsEx1\n", - "// // Hint: The bitstring format is [c2 c1 c0], so c2 is bs[0]\n", - "// // b0, b1 := 0, 0\n", - "// // for bs, n := range countsEx1 { ... }\n", - "//\n", - "// fmt.Printf(\"Expected: P(0) = %.4f, P(1) = %.4f\\n\",\n", - "// \tmath.Cos(theta/2)*math.Cos(theta/2),\n", - "// \tmath.Sin(theta/2)*math.Sin(theta/2))\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above and fill in the missing parts!\")\n", - "_ = theta" - ] - }, - { - "cell_type": "markdown", - "id": "cell-13", - "metadata": {}, - "source": [ - "### Exercise 2: Superdense Coding -- Verify All Four Messages\n", - "\n", - "Build a loop that encodes each of the four 2-bit messages (00, 01, 10, 11)\n", - "and verifies that Bob always decodes the correct message with certainty.\n", - "Display all results in a single summary." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "cell-14", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above and fill in the missing parts!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: Verify superdense coding for all 4 messages\n", - "//\n", - "// TODO: Build a loop over all four 2-bit messages (00, 01, 10, 11).\n", - "// For each message:\n", - "// 1. Create a Bell pair: H(0), CNOT(0,1)\n", - "// 2. Alice encodes by applying X and/or Z to q0 based on the message\n", - "// 3. Bob decodes: CNOT(0,1), H(0), MeasureAll\n", - "// 4. Run the circuit and check that Bob always gets the correct message\n", - "//\n", - "// Hint: 00 -> nothing, 01 -> X, 10 -> Z, 11 -> X then Z\n", - "\n", - "// type sdMessage struct {\n", - "// \tbits string\n", - "// \tapplyX bool\n", - "// \tapplyZ bool\n", - "// }\n", - "//\n", - "// sdMessages := []sdMessage{\n", - "// \t{\"00\", false, false},\n", - "// \t{\"01\", true, false},\n", - "// \t{\"10\", false, true},\n", - "// \t{\"11\", true, true},\n", - "// }\n", - "//\n", - "// allCorrect := true\n", - "// fmt.Println(\"Superdense Coding Verification\")\n", - "// fmt.Println(\"==============================\")\n", - "//\n", - "// for _, msg := range sdMessages {\n", - "// \tb := builder.New(\"sd-\"+msg.bits, 2)\n", - "// \t// TODO: Build Bell pair\n", - "// \t// TODO: Alice encodes based on msg.applyX and msg.applyZ\n", - "// \t// TODO: Bob decodes: CNOT, H, MeasureAll\n", - "//\n", - "// \tc, err := b.Build()\n", - "// \tif err != nil {\n", - "// \t\tpanic(err)\n", - "// \t}\n", - "//\n", - "// \tsim := statevector.New(2)\n", - "// \tcounts, err := sim.Run(c, 1000)\n", - "// \tif err != nil {\n", - "// \t\tpanic(err)\n", - "// \t}\n", - "//\n", - "// \tresult := \"PASS\"\n", - "// \tif counts[msg.bits] != 1000 {\n", - "// \t\tresult = \"FAIL\"\n", - "// \t\tallCorrect = false\n", - "// \t}\n", - "// \tfmt.Printf(\" Message %s -> Bob decoded: %v [%s]\\n\", msg.bits, counts, result)\n", - "// }\n", - "//\n", - "// if allCorrect {\n", - "// \tfmt.Println(\"\\nAll four messages decoded correctly!\")\n", - "// }\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above and fill in the missing parts!\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-15", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "\n", - "1. **Quantum teleportation** transmits an unknown quantum state using one\n", - " shared Bell pair and two classical bits. The quantum state is destroyed\n", - " at the source and recreated at the destination -- no cloning occurs.\n", - "\n", - "2. **Superdense coding** sends two classical bits by transmitting one qubit,\n", - " using a pre-shared Bell pair. It is the \"dual\" of teleportation.\n", - "\n", - "3. Both protocols require **entanglement as a resource**. Without the shared\n", - " Bell pair, neither protocol is possible.\n", - "\n", - "4. Both protocols require **classical communication**. Teleportation needs\n", - " Alice to send her 2-bit measurement result to Bob; superdense coding\n", - " needs Alice to physically send her qubit. Neither violates the\n", - " no-communication theorem or enables faster-than-light signaling.\n", - "\n", - "5. **Dynamic circuits** with mid-circuit measurement and classically\n", - " conditioned gates (feed-forward) are essential for implementing\n", - " teleportation. Goqu supports this via `Measure`, `If`, and `RunDynamic`.\n", - "\n", - "6. The four **Bell states** form a complete basis that enables both protocols.\n", - " In teleportation, Alice's measurement projects onto the Bell basis;\n", - " in superdense coding, Alice encodes by rotating between Bell states." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/07-quantum-fourier-transform.ipynb b/notebooks/07-quantum-fourier-transform.ipynb deleted file mode 100644 index 1491092..0000000 --- a/notebooks/07-quantum-fourier-transform.ipynb +++ /dev/null @@ -1,1167 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a1f7c001", - "metadata": {}, - "source": [ - "# Notebook 07 — The Quantum Fourier Transform\n", - "\n", - "**Prerequisites:** Notebooks 01-06. Familiarity with controlled-phase gates and entanglement.\n", - "\n", - "The **Quantum Fourier Transform (QFT)** is the quantum analog of the classical Discrete Fourier Transform (DFT). While the classical DFT on $N = 2^n$ amplitudes requires $O(N \\log N)$ operations (via the FFT), the QFT accomplishes the same transformation using only $O(n^2)$ quantum gates — an **exponential speedup**.\n", - "\n", - "The QFT is not a standalone algorithm; it is a subroutine that appears at the heart of many of quantum computing's most powerful algorithms:\n", - "\n", - "- **Shor's algorithm** for integer factoring (via order finding)\n", - "- **Quantum Phase Estimation (QPE)** — covered in the next notebook\n", - "- **Quantum counting** and **HHL** (linear systems solver)\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Describe** the QFT circuit structure and its relation to the classical DFT.\n", - "2. **Implement** a QFT manually from H and controlled-phase gates.\n", - "3. **Verify** that QFT followed by inverse QFT is the identity.\n", - "4. **Explain** the phase kickback mechanism.\n", - "\n", - "In this notebook we will:\n", - "\n", - "1. Understand what the QFT does mathematically.\n", - "2. Build a QFT circuit from scratch using H and controlled-phase gates.\n", - "3. Use Goqu's built-in `qpe.QFT` and `qpe.InverseQFT`.\n", - "4. Verify that QFT followed by inverse QFT is the identity.\n", - "5. Observe the QFT's effect on different input states.\n", - "6. Preview **phase kickback**, the mechanism behind QPE.\n", - "7. Explore circuit composition utilities: `ir.Inverse`, `ir.Repeat`, `Compose`, and `ComposeInverse`." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "a1f7c002", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"fmt\"\n", - "\t\"math\"\n", - "\t\"math/cmplx\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/algorithm/qpe\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/circuit/gate\"\n", - "\t\"github.com/splch/goqu/circuit/ir\"\n", - "\t\"github.com/splch/goqu/sim/statevector\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "a1f7c003", - "metadata": {}, - "source": [ - "## What Is the QFT?\n", - "\n", - "The Discrete Fourier Transform maps a vector of $N$ complex amplitudes from the **computational basis** to the **frequency basis**. On $n$ qubits ($N = 2^n$), the QFT acts as:\n", - "\n", - "$$\\text{QFT}|j\\rangle = \\frac{1}{\\sqrt{N}} \\sum_{k=0}^{N-1} e^{2\\pi i \\, jk / N} |k\\rangle$$\n", - "\n", - "Key observations:\n", - "\n", - "- The QFT maps **computational basis states** to states with well-defined **phase structure**.\n", - "- Applied to $|0\\rangle$, every output amplitude has the same phase ($e^{0} = 1$), giving the **uniform superposition**.\n", - "- Applied to $|j\\rangle$ with $j > 0$, the amplitudes acquire phases that increase linearly with $k$ — a frequency encoding.\n", - "- The QFT is a **unitary operation** — it has an inverse, the inverse QFT.\n", - "\n", - "### QFT Circuit Structure\n", - "\n", - "The QFT on $n$ qubits decomposes into:\n", - "\n", - "1. For each qubit $i$ (from 0 to $n-1$):\n", - " - Apply **H** to qubit $i$.\n", - " - For each subsequent qubit $j > i$: apply a **controlled-phase rotation** $\\text{CP}(\\pi / 2^{j-i})$ between qubits $i$ and $j$.\n", - "2. **SWAP** qubits to reverse their order (standard QFT output convention).\n", - "\n", - "This requires $n$ Hadamard gates and $n(n-1)/2$ controlled-phase gates — a total of $O(n^2)$ gates." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "a1f7c004", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Manual 3-qubit QFT circuit:\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "H\n", - "\n", - "\n", - "P(pi/2)\n", - "\n", - "\n", - "P(pi/4)\n", - "H\n", - "\n", - "\n", - "P(pi/2)\n", - "H\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Build a 3-qubit QFT manually using H and CP gates.\n", - "b := builder.New(\"qft-manual\", 3)\n", - "\n", - "// Qubit 0: H, then CP(pi/2) controlled by q1, CP(pi/4) controlled by q2\n", - "b.H(0)\n", - "b.Apply(gate.CP(math.Pi/2), 1, 0)\n", - "b.Apply(gate.CP(math.Pi/4), 2, 0)\n", - "\n", - "// Qubit 1: H, then CP(pi/2) controlled by q2\n", - "b.H(1)\n", - "b.Apply(gate.CP(math.Pi/2), 2, 1)\n", - "\n", - "// Qubit 2: H\n", - "b.H(2)\n", - "\n", - "// SWAP q0 and q2 to match standard QFT output ordering\n", - "b.SWAP(0, 2)\n", - "\n", - "manualQFT, err := b.Build()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Manual 3-qubit QFT circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(manualQFT))" - ] - }, - { - "cell_type": "markdown", - "id": "a1f7c005", - "metadata": {}, - "source": [ - "The circuit above shows the characteristic QFT pattern:\n", - "\n", - "- **Hadamard gates** create superposition on each qubit.\n", - "- **Controlled-phase gates** encode the frequency relationships between qubits. The angle $\\pi / 2^{j-i}$ gets smaller as qubits get farther apart.\n", - "- **SWAP gates** at the end reverse the qubit ordering to match the standard DFT convention.\n", - "\n", - "## Using Goqu's Built-In QFT\n", - "\n", - "Goqu provides `qpe.QFT(n)` which constructs the same circuit automatically. Let's compare." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "a1f7c006", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Built-in 3-qubit QFT circuit:\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "H\n", - "\n", - "\n", - "P(pi/2)\n", - "\n", - "\n", - "P(pi/4)\n", - "H\n", - "\n", - "\n", - "P(pi/2)\n", - "H\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Use the built-in QFT\n", - "builtinQFT, err := qpe.QFT(3)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Built-in 3-qubit QFT circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(builtinQFT))" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "a1f7c007", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Statevectors match after QFT on |101>:\n", - " All amplitudes agree to within 1e-10.\n" - ] - } - ], - "source": [ - "%%\n", - "// Rebuild manualQFT from cell above (needed because %% cells have isolated scope)\n", - "bManual := builder.New(\"qft-manual\", 3)\n", - "bManual.H(0)\n", - "bManual.Apply(gate.CP(math.Pi/2), 1, 0)\n", - "bManual.Apply(gate.CP(math.Pi/4), 2, 0)\n", - "bManual.H(1)\n", - "bManual.Apply(gate.CP(math.Pi/2), 2, 1)\n", - "bManual.H(2)\n", - "bManual.SWAP(0, 2)\n", - "manualQFT, _ := bManual.Build()\n", - "\n", - "// Rebuild builtinQFT (needed because %% cells have isolated scope)\n", - "builtinQFT, _ := qpe.QFT(3)\n", - "\n", - "// Verify both circuits produce the same statevector on a test input.\n", - "// We'll apply each to |101> (decimal 5) and compare.\n", - "prepCircuit := func(name string) *ir.Circuit {\n", - "\tc, _ := builder.New(name, 3).X(0).X(2).Build()\n", - "\treturn c\n", - "}\n", - "\n", - "sim1 := statevector.New(3)\n", - "sim1.Evolve(prepCircuit(\"prep1\"))\n", - "sim1.Apply(manualQFT)\n", - "sv1 := sim1.StateVector()\n", - "\n", - "sim2 := statevector.New(3)\n", - "sim2.Evolve(prepCircuit(\"prep2\"))\n", - "sim2.Apply(builtinQFT)\n", - "sv2 := sim2.StateVector()\n", - "\n", - "fmt.Println(\"Statevectors match after QFT on |101>:\")\n", - "match := true\n", - "for i := range sv1 {\n", - "\tdiff := cmplx.Abs(sv1[i] - sv2[i])\n", - "\tif diff > 1e-10 {\n", - "\t\tmatch = false\n", - "\t\tfmt.Printf(\" MISMATCH at |%03b>: manual=%.4f, builtin=%.4f\\n\", i, sv1[i], sv2[i])\n", - "\t}\n", - "}\n", - "if match {\n", - "\tfmt.Println(\" All amplitudes agree to within 1e-10.\")\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "a1f7c008", - "metadata": {}, - "source": [ - "## The Inverse QFT\n", - "\n", - "Since the QFT is a unitary transformation, it has a well-defined inverse. The **inverse QFT** reverses the frequency-domain encoding and maps back to the computational basis:\n", - "\n", - "$$\\text{QFT}^{-1} \\cdot \\text{QFT} = I$$\n", - "\n", - "The inverse QFT circuit reverses the gate order and conjugates all rotation angles. In Goqu, `qpe.InverseQFT(n)` builds this circuit directly.\n", - "\n", - "Let's verify the identity property: applying QFT then inverse QFT should return the original state." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "a1f7c009", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Inverse QFT circuit:\n", - "Original state |110>:\n", - " |110> : (1.0000+0.0000i)\n", - "\n", - "After QFT (frequency domain):\n", - " |000> : +0.3536 +0.0000i (|amp|=0.3536)\n", - " |001> : -0.3536 +0.0000i (|amp|=0.3536)\n", - " |010> : -0.0000 -0.3536i (|amp|=0.3536)\n", - " |011> : +0.0000 +0.3536i (|amp|=0.3536)\n", - " |100> : -0.2500 +0.2500i (|amp|=0.3536)\n", - " |101> : +0.2500 -0.2500i (|amp|=0.3536)\n", - " |110> : +0.2500 +0.2500i (|amp|=0.3536)\n", - " |111> : -0.2500 -0.2500i (|amp|=0.3536)\n", - "\n", - "After QFT then inverse QFT (round-trip):\n", - " |110> : (1.0000+0.0000i)\n", - "\n", - "Round-trip matches original: true\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "\n", - "\n", - "\n", - "H\n", - "\n", - "\n", - "P(-pi/2)\n", - "H\n", - "\n", - "\n", - "P(-pi/4)\n", - "\n", - "\n", - "P(-pi/2)\n", - "H\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Rebuild builtinQFT (needed because %% cells have isolated scope)\n", - "builtinQFT, _ := qpe.QFT(3)\n", - "\n", - "// Build the inverse QFT\n", - "invQFT, err := qpe.InverseQFT(3)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Inverse QFT circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(invQFT))\n", - "\n", - "// Prepare state |110> (decimal 6)\n", - "prep, _ := builder.New(\"prep\", 3).X(1).X(2).Build()\n", - "\n", - "// Evolve: prep -> QFT -> inverse QFT\n", - "sim := statevector.New(3)\n", - "sim.Evolve(prep)\n", - "svOriginal := sim.StateVector()\n", - "\n", - "sim.Apply(builtinQFT)\n", - "svAfterQFT := sim.StateVector()\n", - "\n", - "sim.Apply(invQFT)\n", - "svRoundTrip := sim.StateVector()\n", - "\n", - "fmt.Println(\"Original state |110>:\")\n", - "for i, amp := range svOriginal {\n", - "\tif cmplx.Abs(amp) > 1e-10 {\n", - "\t\tfmt.Printf(\" |%03b> : %.4f\\n\", i, amp)\n", - "\t}\n", - "}\n", - "\n", - "fmt.Println(\"\\nAfter QFT (frequency domain):\")\n", - "for i, amp := range svAfterQFT {\n", - "\tif cmplx.Abs(amp) > 1e-10 {\n", - "\t\tfmt.Printf(\" |%03b> : %+.4f %+.4fi (|amp|=%.4f)\\n\", i, real(amp), imag(amp), cmplx.Abs(amp))\n", - "\t}\n", - "}\n", - "\n", - "fmt.Println(\"\\nAfter QFT then inverse QFT (round-trip):\")\n", - "for i, amp := range svRoundTrip {\n", - "\tif cmplx.Abs(amp) > 1e-10 {\n", - "\t\tfmt.Printf(\" |%03b> : %.4f\\n\", i, amp)\n", - "\t}\n", - "}\n", - "\n", - "// Verify the round-trip reproduces the original\n", - "match := true\n", - "for i := range svOriginal {\n", - "\tif cmplx.Abs(svOriginal[i]-svRoundTrip[i]) > 1e-10 {\n", - "\t\tmatch = false\n", - "\t\tbreak\n", - "\t}\n", - "}\n", - "fmt.Printf(\"\\nRound-trip matches original: %v\\n\", match)" - ] - }, - { - "cell_type": "markdown", - "id": "a1f7c010", - "metadata": {}, - "source": [ - "## QFT on Specific States\n", - "\n", - "To build intuition, let's observe the QFT's output on several computational basis states.\n", - "\n", - "Recall the QFT formula: $\\text{QFT}|j\\rangle = \\frac{1}{\\sqrt{N}} \\sum_{k} e^{2\\pi i jk/N} |k\\rangle$\n", - "\n", - "- **$|000\\rangle$ ($j=0$):** All phases are $e^{0} = 1$, producing a uniform superposition with equal real amplitudes.\n", - "- **$|001\\rangle$ and $|010\\rangle$:** The QFT encodes different input values as distinct phase patterns in the output. The exact phase distribution depends on the qubit ordering convention (the QFT circuit processes qubit 0 first, treating it as the most significant bit internally).\n", - "\n", - "The important point: each input state produces a **unique phase fingerprint** that the inverse QFT can decode back to the original state." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "a1f7c011", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== QFT|000> ===\n", - " |000> : amp = +0.3536 +0.0000i |amp| = 0.3536 phase = 0.000 rad\n", - " |001> : amp = +0.3536 +0.0000i |amp| = 0.3536 phase = 0.000 rad\n", - " |010> : amp = +0.3536 +0.0000i |amp| = 0.3536 phase = 0.000 rad\n", - " |011> : amp = +0.3536 +0.0000i |amp| = 0.3536 phase = 0.000 rad\n", - " |100> : amp = +0.3536 +0.0000i |amp| = 0.3536 phase = 0.000 rad\n", - " |101> : amp = +0.3536 +0.0000i |amp| = 0.3536 phase = 0.000 rad\n", - " |110> : amp = +0.3536 +0.0000i |amp| = 0.3536 phase = 0.000 rad\n", - " |111> : amp = +0.3536 +0.0000i |amp| = 0.3536 phase = 0.000 rad\n", - " Expected: uniform amplitudes 1/sqrt(8) = 0.3536 with linearly increasing phases\n", - "\n", - "=== QFT|001> ===\n", - " |000> : amp = +0.3536 +0.0000i |amp| = 0.3536 phase = 0.000 rad\n", - " |001> : amp = +0.3536 +0.0000i |amp| = 0.3536 phase = 0.000 rad\n", - " |010> : amp = +0.3536 +0.0000i |amp| = 0.3536 phase = 0.000 rad\n", - " |011> : amp = +0.3536 +0.0000i |amp| = 0.3536 phase = 0.000 rad\n", - " |100> : amp = -0.3536 +0.0000i |amp| = 0.3536 phase = 3.142 rad\n", - " |101> : amp = -0.3536 +0.0000i |amp| = 0.3536 phase = 3.142 rad\n", - " |110> : amp = -0.3536 +0.0000i |amp| = 0.3536 phase = 3.142 rad\n", - " |111> : amp = -0.3536 +0.0000i |amp| = 0.3536 phase = 3.142 rad\n", - " Expected: uniform amplitudes 1/sqrt(8) = 0.3536 with linearly increasing phases\n", - "\n", - "=== QFT|010> ===\n", - " |000> : amp = +0.3536 +0.0000i |amp| = 0.3536 phase = 0.000 rad\n", - " |001> : amp = +0.3536 +0.0000i |amp| = 0.3536 phase = 0.000 rad\n", - " |010> : amp = -0.3536 +0.0000i |amp| = 0.3536 phase = 3.142 rad\n", - " |011> : amp = -0.3536 +0.0000i |amp| = 0.3536 phase = 3.142 rad\n", - " |100> : amp = +0.0000 +0.3536i |amp| = 0.3536 phase = 1.571 rad\n", - " |101> : amp = +0.0000 +0.3536i |amp| = 0.3536 phase = 1.571 rad\n", - " |110> : amp = -0.0000 -0.3536i |amp| = 0.3536 phase = -1.571 rad\n", - " |111> : amp = -0.0000 -0.3536i |amp| = 0.3536 phase = -1.571 rad\n", - " Expected: uniform amplitudes 1/sqrt(8) = 0.3536 with linearly increasing phases\n", - "\n" - ] - } - ], - "source": [ - "%%\n", - "// Apply QFT to |000>, |001>, and |010> and display the results.\n", - "qftCirc, _ := qpe.QFT(3)\n", - "N := 8.0 // 2^3\n", - "\n", - "testStates := []struct {\n", - "\tname string\n", - "\tsetup func(b *builder.Builder)\n", - "}{\n", - "\t{\"000\", func(b *builder.Builder) {}}, // |000> = j=0\n", - "\t{\"001\", func(b *builder.Builder) { b.X(0) }}, // |001> = j=1\n", - "\t{\"010\", func(b *builder.Builder) { b.X(1) }}, // |010> = j=2\n", - "}\n", - "\n", - "for _, ts := range testStates {\n", - "\tfmt.Printf(\"=== QFT|%s> ===\\n\", ts.name)\n", - "\n", - "\tb := builder.New(\"prep-\"+ts.name, 3)\n", - "\tts.setup(b)\n", - "\tprep, _ := b.Build()\n", - "\n", - "\tsim := statevector.New(3)\n", - "\tsim.Evolve(prep)\n", - "\tsim.Apply(qftCirc)\n", - "\tsv := sim.StateVector()\n", - "\n", - "\tfor k, amp := range sv {\n", - "\t\tphase := cmplx.Phase(amp)\n", - "\t\tif cmplx.Abs(amp) < 1e-10 {\n", - "\t\t\tphase = 0\n", - "\t\t}\n", - "\t\tfmt.Printf(\" |%03b> : amp = %+.4f %+.4fi |amp| = %.4f phase = %.3f rad\\n\",\n", - "\t\t\tk, real(amp), imag(amp), cmplx.Abs(amp), phase)\n", - "\t}\n", - "\tfmt.Printf(\" Expected: uniform amplitudes 1/sqrt(%v) = %.4f with linearly increasing phases\\n\\n\",\n", - "\t\tN, 1/math.Sqrt(N))\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "a1f7c012", - "metadata": {}, - "source": [ - "Notice the pattern:\n", - "\n", - "- **QFT|000>**: All amplitudes are $1/\\sqrt{8} \\approx 0.3536$ with zero phase — a uniform superposition. This is the \"zero frequency\" — no oscillation.\n", - "- **QFT|001>** and **QFT|010>**: All amplitudes have the same magnitude $1/\\sqrt{8}$, but with distinct phase patterns. Each input state produces a unique phase fingerprint.\n", - "- The amplitudes are always **uniform** ($1/\\sqrt{N}$) — only the phases differ between inputs.\n", - "\n", - "The QFT encodes the input value $j$ as a **phase structure** in the output amplitudes. This is the key to algorithms like QPE, where we use the inverse QFT to extract a phase that has been encoded in a quantum register.\n", - "\n", - "**Note on qubit ordering:** The QFT circuit processes qubit 0 first, effectively treating it as the most significant bit within the transform. Combined with the bitstring convention ($|q_2\\,q_1\\,q_0\\rangle$, qubit 0 rightmost), the phase patterns may not appear as simple linear progressions when listed by state index. The important property is that QFT followed by inverse QFT is the identity — the encoding is perfectly reversible regardless of convention.\n", - "\n", - "## Phase Kickback\n", - "\n", - "**Phase kickback** is the mechanism that makes QPE work. The idea is:\n", - "\n", - "1. Prepare a control qubit in superposition: $|+\\rangle = (|0\\rangle + |1\\rangle)/\\sqrt{2}$.\n", - "2. Apply a controlled-U gate where the target is an **eigenstate** of U with eigenvalue $e^{i\\phi}$.\n", - "3. The eigenvalue phase \"kicks back\" onto the control qubit: $(|0\\rangle + e^{i\\phi}|1\\rangle)/\\sqrt{2}$.\n", - "\n", - "The target qubit is unchanged (it's an eigenstate), but the control qubit acquires the eigenvalue phase. This is how QPE encodes the phase into a register that the inverse QFT can then read out." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "a1f7c013", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Phase kickback circuit:\n", - "Statevector after phase kickback:\n", - " |10> : +0.7071 +0.0000i (phase = 0.0000 rad)\n", - " |11> : +0.5000 +0.5000i (phase = 0.7854 rad)\n", - "\n", - "Interpretation:\n", - " Target qubit remains |1> in both terms — it is unchanged.\n", - " Control qubit: |01> has phase 0, |11> has phase pi/4 = 0.7854 rad\n", - " The eigenvalue phase has 'kicked back' onto the control qubit.\n", - " This is the core mechanism of Quantum Phase Estimation.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "X\n", - "H\n", - "\n", - "\n", - "T\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Demonstrate phase kickback.\n", - "// The T gate has eigenvalue e^{i*pi/4} on eigenstate |1>.\n", - "// We use a controlled-T with the control in |+> and target in |1>.\n", - "// Expected: control acquires phase pi/4, target stays |1>.\n", - "\n", - "cKick, err := builder.New(\"phase-kickback\", 2).\n", - "\tX(1). // target = |1> (eigenstate of T)\n", - "\tH(0). // control = |+>\n", - "\tCtrl(gate.T, []int{0}, 1). // controlled-T\n", - "\tBuild()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Phase kickback circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(cKick))\n", - "\n", - "sim := statevector.New(2)\n", - "if err := sim.Evolve(cKick); err != nil {\n", - "\tpanic(err)\n", - "}\n", - "sv := sim.StateVector()\n", - "\n", - "fmt.Println(\"Statevector after phase kickback:\")\n", - "for i, amp := range sv {\n", - "\tif cmplx.Abs(amp) > 1e-10 {\n", - "\t\tfmt.Printf(\" |%02b> : %+.4f %+.4fi (phase = %.4f rad)\\n\",\n", - "\t\t\ti, real(amp), imag(amp), cmplx.Phase(amp))\n", - "\t}\n", - "}\n", - "\n", - "fmt.Println(\"\\nInterpretation:\")\n", - "fmt.Println(\" Target qubit remains |1> in both terms — it is unchanged.\")\n", - "fmt.Printf(\" Control qubit: |01> has phase 0, |11> has phase pi/4 = %.4f rad\\n\", math.Pi/4)\n", - "fmt.Println(\" The eigenvalue phase has 'kicked back' onto the control qubit.\")\n", - "fmt.Println(\" This is the core mechanism of Quantum Phase Estimation.\")" - ] - }, - { - "cell_type": "markdown", - "id": "a1f7c014", - "metadata": {}, - "source": [ - "## Composing Circuits\n", - "\n", - "Goqu provides several utilities for building complex circuits from simpler parts:\n", - "\n", - "| Function | Description |\n", - "|:---|:---|\n", - "| `ir.Inverse(c)` | Reverses gate order and adjoints each gate (the \"dagger\" operation) |\n", - "| `ir.Repeat(c, n)` | Concatenates `c`'s operations `n` times |\n", - "| `builder.Compose(c, qubitMap)` | Appends circuit `c` into the builder with qubit remapping |\n", - "| `builder.ComposeInverse(c, qubitMap)` | Appends the inverse of `c` into the builder |\n", - "\n", - "These are essential tools for building algorithms that reuse subcircuits — like applying QFT and then inverse QFT, or repeating a unitary $U^{2^k}$ times in QPE." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "a1f7c015", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "QFT circuit:\n", - "ir.Inverse(QFT) — the adjoint:\n", - "T gate repeated 3 times:\n", - "Compose QFT then ComposeInverse QFT on |101>:\n", - " |101> : (1.0000+0.0000i)\n", - "Result: |101> recovered — the two operations cancel.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "H\n", - "\n", - "\n", - "P(pi/2)\n", - "\n", - "\n", - "P(pi/4)\n", - "H\n", - "\n", - "\n", - "P(pi/2)\n", - "H\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "\n", - "\n", - "\n", - "H\n", - "\n", - "\n", - "P(-pi/2)\n", - "H\n", - "\n", - "\n", - "P(-pi/4)\n", - "\n", - "\n", - "P(-pi/2)\n", - "H\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "T\n", - "T\n", - "T\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Demonstrate ir.Inverse: build QFT then invert it\n", - "qftCirc, _ := qpe.QFT(3)\n", - "qftInv := ir.Inverse(qftCirc)\n", - "\n", - "fmt.Println(\"QFT circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(qftCirc))\n", - "fmt.Println(\"ir.Inverse(QFT) — the adjoint:\")\n", - "gonbui.DisplayHTML(draw.SVG(qftInv))\n", - "\n", - "// Demonstrate ir.Repeat: repeat a small circuit 3 times\n", - "smallCirc, _ := builder.New(\"small\", 1).T(0).Build()\n", - "repeated, err := ir.Repeat(smallCirc, 3)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "fmt.Println(\"T gate repeated 3 times:\")\n", - "gonbui.DisplayHTML(draw.SVG(repeated))\n", - "\n", - "// Demonstrate Compose and ComposeInverse\n", - "b := builder.New(\"round-trip\", 3)\n", - "b.X(0).X(2) // Prepare |101>\n", - "b.Compose(qftCirc, nil) // Apply QFT\n", - "b.ComposeInverse(qftCirc, nil) // Apply QFT-dagger (= inverse QFT)\n", - "roundTrip, _ := b.Build()\n", - "\n", - "sim := statevector.New(3)\n", - "sim.Evolve(roundTrip)\n", - "sv := sim.StateVector()\n", - "\n", - "fmt.Println(\"Compose QFT then ComposeInverse QFT on |101>:\")\n", - "for i, amp := range sv {\n", - "\tif cmplx.Abs(amp) > 1e-10 {\n", - "\t\tfmt.Printf(\" |%03b> : %.4f\\n\", i, amp)\n", - "\t}\n", - "}\n", - "fmt.Println(\"Result: |101> recovered — the two operations cancel.\")" - ] - }, - { - "cell_type": "markdown", - "id": "a1f7c016", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** What does QFT|000> produce? What will the measurement distribution look like?\n", - "\n", - "**Pause and predict** before running the next cell.\n", - "\n", - "*Hint: Think about the QFT formula with $j = 0$. What happens to the phase factor $e^{2\\pi i \\cdot 0 \\cdot k / N}$?*" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "a1f7c017", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "QFT|000> statevector:\n", - " |000> : 0.353553 (expected: 0.353553)\n", - " |001> : 0.353553 (expected: 0.353553)\n", - " |010> : 0.353553 (expected: 0.353553)\n", - " |011> : 0.353553 (expected: 0.353553)\n", - " |100> : 0.353553 (expected: 0.353553)\n", - " |101> : 0.353553 (expected: 0.353553)\n", - " |110> : 0.353553 (expected: 0.353553)\n", - " |111> : 0.353553 (expected: 0.353553)\n", - "\n", - "All amplitudes equal to 1/sqrt(8): true\n", - "\n", - "Measurement results (8000 shots, expect ~1000 each):\n", - " |110> : 1031 (12.9%)\n", - " |111> : 952 (11.9%)\n", - " |000> : 1031 (12.9%)\n", - " |010> : 998 (12.5%)\n", - " |100> : 1025 (12.8%)\n", - " |001> : 1059 (13.2%)\n", - " |011> : 950 (11.9%)\n", - " |101> : 954 (11.9%)\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "QFT|000> Measurement Distribution\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "200\n", - "\n", - "400\n", - "\n", - "600\n", - "\n", - "800\n", - "\n", - "1000\n", - "\n", - "1200\n", - "\n", - "000\n", - "\n", - "001\n", - "\n", - "010\n", - "\n", - "011\n", - "\n", - "100\n", - "\n", - "101\n", - "\n", - "110\n", - "\n", - "111\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Verify: QFT|000> should give uniform superposition\n", - "qftCirc, _ := qpe.QFT(3)\n", - "\n", - "sim := statevector.New(3)\n", - "prep, _ := builder.New(\"zero\", 3).Build()\n", - "sim.Evolve(prep)\n", - "sim.Apply(qftCirc)\n", - "sv := sim.StateVector()\n", - "\n", - "fmt.Println(\"QFT|000> statevector:\")\n", - "allEqual := true\n", - "expectedAmp := 1.0 / math.Sqrt(8.0)\n", - "for k, amp := range sv {\n", - "\tfmt.Printf(\" |%03b> : %.6f (expected: %.6f)\\n\", k, real(amp), expectedAmp)\n", - "\tif cmplx.Abs(amp-complex(expectedAmp, 0)) > 1e-10 {\n", - "\t\tallEqual = false\n", - "\t}\n", - "}\n", - "fmt.Printf(\"\\nAll amplitudes equal to 1/sqrt(8): %v\\n\", allEqual)\n", - "\n", - "// Measure to show uniform distribution\n", - "bMeasure := builder.New(\"qft-measure\", 3)\n", - "qpe.ApplyQFT(bMeasure, 3)\n", - "bMeasure.MeasureAll()\n", - "cMeasure, _ := bMeasure.Build()\n", - "\n", - "simM := statevector.New(3)\n", - "counts, _ := simM.Run(cMeasure, 8000)\n", - "\n", - "fmt.Println(\"\\nMeasurement results (8000 shots, expect ~1000 each):\")\n", - "for outcome, count := range counts {\n", - "\tfmt.Printf(\" |%s> : %d (%.1f%%)\\n\", outcome, count, float64(count)/80.0)\n", - "}\n", - "\n", - "svgHist := viz.Histogram(counts, viz.WithTitle(\"QFT|000> Measurement Distribution\"))\n", - "gonbui.DisplayHTML(svgHist)" - ] - }, - { - "cell_type": "markdown", - "id": "0jaey7e39kj8", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. What does the QFT do to the computational basis state |0...0⟩?\n", - "2. Why is the inverse QFT used in QPE rather than the forward QFT?\n", - "3. How many controlled-phase gates are needed for an n-qubit QFT, and why is this still exponentially better than the classical FFT?" - ] - }, - { - "cell_type": "markdown", - "id": "a1f7c018", - "metadata": {}, - "source": [ - "## Exercises\n", - "\n", - "### Exercise 1 — Build a 4-Qubit QFT Manually\n", - "\n", - "Extend the manual construction pattern to 4 qubits. You will need:\n", - "- Qubit 0: H, CP($\\pi/2$) with q1, CP($\\pi/4$) with q2, CP($\\pi/8$) with q3\n", - "- Qubit 1: H, CP($\\pi/2$) with q2, CP($\\pi/4$) with q3\n", - "- Qubit 2: H, CP($\\pi/2$) with q3\n", - "- Qubit 3: H\n", - "- SWAPs: (0,3) and (1,2)\n", - "\n", - "Verify your circuit matches `qpe.QFT(4)` by comparing statevectors on an input state." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "a1f7c019", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above and fill in the missing parts!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: Build 4-qubit QFT manually\n", - "//\n", - "// TODO: Extend the manual QFT pattern to 4 qubits:\n", - "// - Qubit 0: H, CP(pi/2) with q1, CP(pi/4) with q2, CP(pi/8) with q3\n", - "// - Qubit 1: H, CP(pi/2) with q2, CP(pi/4) with q3\n", - "// - Qubit 2: H, CP(pi/2) with q3\n", - "// - Qubit 3: H\n", - "// - SWAPs: (0,3) and (1,2)\n", - "// Then compare your circuit with qpe.QFT(4) on a test input.\n", - "//\n", - "// Hint: Use b.Apply(gate.CP(angle), control, target) for controlled-phase gates\n", - "\n", - "// b := builder.New(\"qft4-manual\", 4)\n", - "//\n", - "// // TODO: Qubit 0 -- H and three controlled-phase gates\n", - "//\n", - "// // TODO: Qubit 1 -- H and two controlled-phase gates\n", - "//\n", - "// // TODO: Qubit 2 -- H and one controlled-phase gate\n", - "//\n", - "// // TODO: Qubit 3 -- H only\n", - "//\n", - "// // TODO: SWAPs to reverse qubit order\n", - "//\n", - "// manual4, err := b.Build()\n", - "// if err != nil {\n", - "// \tpanic(err)\n", - "// }\n", - "//\n", - "// fmt.Println(\"Manual 4-qubit QFT:\")\n", - "// fmt.Println(draw.String(manual4))\n", - "//\n", - "// // Compare with built-in on |1010>\n", - "// builtin4, _ := qpe.QFT(4)\n", - "// prep, _ := builder.New(\"prep\", 4).X(1).X(3).Build()\n", - "//\n", - "// sim1 := statevector.New(4)\n", - "// sim1.Evolve(prep)\n", - "// sim1.Apply(manual4)\n", - "// sv1 := sim1.StateVector()\n", - "//\n", - "// sim2 := statevector.New(4)\n", - "// sim2.Evolve(prep)\n", - "// sim2.Apply(builtin4)\n", - "// sv2 := sim2.StateVector()\n", - "//\n", - "// match := true\n", - "// for i := range sv1 {\n", - "// \tif cmplx.Abs(sv1[i]-sv2[i]) > 1e-10 {\n", - "// \t\tmatch = false\n", - "// \t\tbreak\n", - "// \t}\n", - "// }\n", - "// fmt.Printf(\"\\nManual 4-qubit QFT matches built-in on |1010>: %v\\n\", match)\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above and fill in the missing parts!\")" - ] - }, - { - "cell_type": "markdown", - "id": "a1f7c020", - "metadata": {}, - "source": [ - "### Exercise 2 — Verify QFT/Inverse-QFT Identity on a Superposition State\n", - "\n", - "Create a non-trivial 3-qubit state by applying some gates (e.g., H on qubit 0, CNOT(0,1), T on qubit 2), then apply QFT followed by inverse QFT. Verify the original statevector is recovered." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "a1f7c021", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above and fill in the missing parts!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: QFT -> inverse QFT is identity on an arbitrary state\n", - "//\n", - "// TODO: Create a non-trivial 3-qubit state, apply QFT then inverse QFT,\n", - "// and verify the original statevector is recovered.\n", - "//\n", - "// Steps:\n", - "// 1. Prepare a state: e.g., H(0), CNOT(0,1), T(2)\n", - "// 2. Record the statevector before the round-trip\n", - "// 3. Apply qpe.QFT(3) then qpe.InverseQFT(3)\n", - "// 4. Compare the final statevector with the original\n", - "//\n", - "// Hint: Use sim.Apply(circuit) to apply a pre-built circuit to the simulator\n", - "\n", - "// prep, _ := builder.New(\"arbitrary\", 3).\n", - "// \tH(0).\n", - "// \tCNOT(0, 1).\n", - "// \tT(2).\n", - "// \tBuild()\n", - "//\n", - "// sim := statevector.New(3)\n", - "// sim.Evolve(prep)\n", - "// svBefore := sim.StateVector()\n", - "//\n", - "// // TODO: Build QFT and inverse QFT circuits\n", - "// // qftCirc, _ := qpe.QFT(3)\n", - "// // invCirc, _ := qpe.InverseQFT(3)\n", - "//\n", - "// // TODO: Apply QFT then inverse QFT\n", - "// // sim.Apply(qftCirc)\n", - "// // sim.Apply(invCirc)\n", - "// // svAfter := sim.StateVector()\n", - "//\n", - "// // TODO: Compare svBefore and svAfter\n", - "// // match := true\n", - "// // for i := range svBefore {\n", - "// // \tif cmplx.Abs(svBefore[i]-svAfter[i]) > 1e-10 {\n", - "// // \t\tmatch = false\n", - "// // \t\tbreak\n", - "// // \t}\n", - "// // }\n", - "// // fmt.Printf(\"Original state recovered: %v\\n\", match)\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above and fill in the missing parts!\")" - ] - }, - { - "cell_type": "markdown", - "id": "a1f7c022", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "\n", - "1. **The QFT** maps computational basis states to the frequency basis: $\\text{QFT}|j\\rangle = \\frac{1}{\\sqrt{N}} \\sum_k e^{2\\pi i jk/N} |k\\rangle$. It uses $O(n^2)$ gates versus the classical FFT's $O(N \\log N)$ — an exponential improvement.\n", - "\n", - "2. **Circuit structure:** H gates create superposition on each qubit; controlled-phase gates $\\text{CP}(\\pi/2^{j-i})$ encode the frequency relationships; final SWAPs fix the output ordering.\n", - "\n", - "3. **The inverse QFT** reverses the transformation. QFT followed by inverse QFT is the identity on any state.\n", - "\n", - "4. **QFT on $|0\\rangle$** produces a uniform superposition. QFT on $|j\\rangle$ produces uniform amplitudes with phases that increase at frequency $j$.\n", - "\n", - "5. **Phase kickback** transfers an eigenvalue phase from a controlled unitary onto the control qubit. This is how QPE encodes phases into a register for the inverse QFT to decode.\n", - "\n", - "6. **Circuit composition** tools (`ir.Inverse`, `ir.Repeat`, `Compose`, `ComposeInverse`) make it easy to build complex algorithms from reusable subcircuits.\n", - "\n", - "---\n", - "\n", - "**Next up:** Notebook 08 — Quantum Phase Estimation, where we combine the QFT, phase kickback, and controlled unitaries into a complete algorithm for extracting eigenvalue phases." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/08-phase-estimation.ipynb b/notebooks/08-phase-estimation.ipynb deleted file mode 100644 index 94663da..0000000 --- a/notebooks/08-phase-estimation.ipynb +++ /dev/null @@ -1,616 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a1b2c3d4", - "metadata": {}, - "source": [ - "# Notebook 08 -- Quantum Phase Estimation\n", - "\n", - "**Prerequisites:** Notebooks 01-07. Familiarity with the QFT and phase kickback.\n", - "\n", - "**Quantum Phase Estimation (QPE)** is the most important subroutine in\n", - "quantum computing. It extracts the eigenvalue phase of a unitary operator\n", - "and underpins Shor's algorithm, quantum chemistry, and the HHL algorithm.\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Describe** QPE and predict the phase it extracts from a known unitary.\n", - "2. **Implement** QPE using Goqu's `qpe` package.\n", - "3. **Use** state preparation to initialize arbitrary quantum states.\n", - "4. **Compare** QPE precision with different numbers of phase bits.\n", - "\n", - "For textbook oracle algorithms (Deutsch-Jozsa, Bernstein-Vazirani, Simon's), see [Notebook 08b](08b-textbook-algorithms.ipynb)." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "b2c3d4e5", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"context\"\n", - "\t\"fmt\"\n", - "\t\"math\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/algorithm/qpe\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/circuit/gate\"\n", - "\t\"github.com/splch/goqu/sim/statevector\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "f2a3b4c5", - "metadata": {}, - "source": [ - "## Quantum Phase Estimation (QPE)\n", - "\n", - "**Problem:** Given a unitary operator $U$ and one of its eigenstates\n", - "$|\\psi\\rangle$ satisfying $U|\\psi\\rangle = e^{2\\pi i \\varphi}|\\psi\\rangle$,\n", - "estimate the phase $\\varphi$.\n", - "\n", - "QPE is the most important subroutine in quantum computing. It underpins:\n", - "- **Shor's algorithm** (factoring via order-finding)\n", - "- **Quantum chemistry** (energy estimation of molecular Hamiltonians)\n", - "- **HHL algorithm** (linear systems solver)\n", - "\n", - "**How it works:**\n", - "\n", - "1. Prepare a **phase register** of $t$ qubits in superposition.\n", - "2. Apply controlled-$U^{2^k}$ operations to transfer phase information.\n", - "3. Apply the **inverse Quantum Fourier Transform** (QFT$^\\dagger$) to convert\n", - " phase into a binary fraction readable by measurement.\n", - "\n", - "With $t$ phase bits, QPE estimates $\\varphi$ to $t$ bits of precision:\n", - "$\\hat{\\varphi} = m / 2^t$ where $m$ is the measured integer.\n", - "\n", - "For the **T gate** ($\\pi/8$ gate), the eigenvalue on $|1\\rangle$ is\n", - "$e^{i\\pi/4} = e^{2\\pi i \\cdot 1/8}$, so the phase is $\\varphi = 1/8 = 0.125$." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "a3b4c5d6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== Quantum Phase Estimation ===\n", - "Unitary: T gate (phase = pi/4)\n", - "Phase bits: 3\n", - "Estimated phase: 0.1250 (expected: 0.125)\n", - "Phase as fraction: 1/8 = 0.1250\n", - "Measurement counts: map[1100:1000]\n" - ] - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// QPE on the T gate: T|1> = exp(i*pi/4)|1> = exp(2*pi*i * 1/8)|1>\n", - "// So the phase is 1/8 = 0.125\n", - "eigenCircuit, err := builder.New(\"eigen\", 1).X(0).Build()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "result, err := qpe.Run(ctx, qpe.Config{\n", - "\tUnitary: gate.T,\n", - "\tNumPhaseBits: 3,\n", - "\tEigenState: eigenCircuit,\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"=== Quantum Phase Estimation ===\")\n", - "fmt.Printf(\"Unitary: T gate (phase = pi/4)\\n\")\n", - "fmt.Printf(\"Phase bits: %d\\n\", result.PhaseRegBits)\n", - "fmt.Printf(\"Estimated phase: %.4f (expected: 0.125)\\n\", result.Phase)\n", - "fmt.Printf(\"Phase as fraction: 1/8 = %.4f\\n\", 1.0/8.0)\n", - "fmt.Println(\"Measurement counts:\", result.Counts)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "b4c5d6e7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "QPE Circuit:\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "q3\n", - "\n", - "X\n", - "H\n", - "H\n", - "H\n", - "\n", - "\n", - "T\n", - "\n", - "\n", - "T\n", - "\n", - "\n", - "T\n", - "\n", - "\n", - "T\n", - "\n", - "\n", - "T\n", - "\n", - "\n", - "T\n", - "\n", - "\n", - "T\n", - "\n", - "\n", - "\n", - "H\n", - "\n", - "\n", - "P(-pi/2)\n", - "H\n", - "\n", - "\n", - "P(-pi/4)\n", - "\n", - "\n", - "P(-pi/2)\n", - "H\n", - "M\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "QPE: T Gate (expected phase = 0.125)\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "200\n", - "\n", - "400\n", - "\n", - "600\n", - "\n", - "800\n", - "\n", - "1000\n", - "\n", - "1100\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "eigenCircuit, _ := builder.New(\"eigen\", 1).X(0).Build()\n", - "\n", - "result, _ := qpe.Run(ctx, qpe.Config{\n", - "\tUnitary: gate.T,\n", - "\tNumPhaseBits: 3,\n", - "\tEigenState: eigenCircuit,\n", - "\tShots: 1000,\n", - "})\n", - "\n", - "fmt.Println(\"QPE Circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(result.Circuit))\n", - "\n", - "svg := viz.Histogram(result.Counts, viz.WithTitle(\"QPE: T Gate (expected phase = 0.125)\"))\n", - "gonbui.DisplayHTML(svg)" - ] - }, - { - "cell_type": "markdown", - "id": "c5d6e7f8", - "metadata": {}, - "source": [ - "## State Preparation\n", - "\n", - "Many quantum algorithms require initializing qubits in a specific state\n", - "beyond the standard $|0\\rangle$. The `gate.MustStatePrep` function creates\n", - "a gate that prepares any normalized state from a list of amplitudes.\n", - "\n", - "Internally, it uses a Mottonen decomposition -- a sequence of uniformly\n", - "controlled RY and RZ rotations that systematically constructs the target\n", - "state from $|0\\rangle$.\n", - "\n", - "Let's prepare the state $\\frac{1}{2}|0\\rangle + \\frac{\\sqrt{3}}{2}|1\\rangle$,\n", - "which has $P(0) = 1/4$ and $P(1) = 3/4$." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "d6e7f8a9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "State preparation: (1/2)|0> + (sqrt(3)/2)|1>\n", - "Expected: P(0) = 0.25, P(1) = 0.75\n", - "Measured counts (2000 shots): map[0:463 1:1537]\n", - " |1> : 1537 (76.8%)\n", - " |0> : 463 (23.1%)\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "State Preparation: P(0)=0.25, P(1)=0.75\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "500\n", - "\n", - "1000\n", - "\n", - "1500\n", - "\n", - "2000\n", - "\n", - "0\n", - "\n", - "1\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Prepare a known state using gate.MustStatePrep\n", - "// Target: (1/2)|0> + (sqrt(3)/2)|1> => P(0)=0.25, P(1)=0.75\n", - "amplitudes := []complex128{\n", - "\tcomplex(0.5, 0), // amplitude for |0>\n", - "\tcomplex(math.Sqrt(3)/2, 0), // amplitude for |1>\n", - "}\n", - "\n", - "spGate := gate.MustStatePrep(amplitudes)\n", - "\n", - "c, err := builder.New(\"state-prep\", 1).\n", - "\tApply(spGate, 0).\n", - "\tMeasureAll().\n", - "\tBuild()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "sim := statevector.New(1)\n", - "counts, err := sim.Run(c, 2000)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"State preparation: (1/2)|0> + (sqrt(3)/2)|1>\")\n", - "fmt.Println(\"Expected: P(0) = 0.25, P(1) = 0.75\")\n", - "fmt.Println(\"Measured counts (2000 shots):\", counts)\n", - "for outcome, count := range counts {\n", - "\tfmt.Printf(\" |%s> : %d (%.1f%%)\\n\", outcome, count, float64(count)/20.0)\n", - "}\n", - "\n", - "svg := viz.Histogram(counts, viz.WithTitle(\"State Preparation: P(0)=0.25, P(1)=0.75\"))\n", - "gonbui.DisplayHTML(svg)" - ] - }, - { - "cell_type": "markdown", - "id": "e7f8a9b0", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** The S gate has the property $S|1\\rangle = e^{i\\pi/2}|1\\rangle$. If we run QPE with 3 phase bits on the S gate, what phase $\\varphi$ will it extract?\n", - "\n", - "**Pause and predict** before running the next cell.\n", - "\n", - "*Hint: Express the eigenvalue as $e^{2\\pi i \\varphi}$ and solve for $\\varphi$. Is this phase exactly representable in 3 binary digits?*" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "f8a9b0c1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "S gate -- Estimated phase: 0.2500 (expected: 0.2500)\n", - "Match: true\n", - "Counts: map[1010:1000]\n", - "\n", - "Prediction confirmed: S gate phase = 1/4 = 0.25.\n" - ] - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Verify: QPE on the S gate should give phase = 0.25\n", - "eigenCircuit, _ := builder.New(\"eigen\", 1).X(0).Build()\n", - "\n", - "result, err := qpe.Run(ctx, qpe.Config{\n", - "\tUnitary: gate.S,\n", - "\tNumPhaseBits: 3,\n", - "\tEigenState: eigenCircuit,\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"S gate -- Estimated phase: %.4f (expected: 0.2500)\\n\", result.Phase)\n", - "fmt.Printf(\"Match: %v\\n\", math.Abs(result.Phase-0.25) < 0.01)\n", - "fmt.Println(\"Counts:\", result.Counts)\n", - "fmt.Println(\"\\nPrediction confirmed: S gate phase = 1/4 = 0.25.\")" - ] - }, - { - "cell_type": "markdown", - "id": "4tccltkkjm", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. How many queries does QPE need to estimate a phase to t bits of precision?\n", - "2. Why does QPE require the inverse QFT rather than the forward QFT?\n", - "3. What happens when the true phase is not exactly representable with the available number of phase bits?" - ] - }, - { - "cell_type": "markdown", - "id": "a9b0c1d2", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## Exercises\n", - "\n", - "### Exercise 1 -- QPE on a Custom Unitary\n", - "\n", - "Run QPE on the **Z gate**. The Z gate applies a phase of $e^{i\\pi}$ to\n", - "$|1\\rangle$, which corresponds to $e^{2\\pi i \\cdot 1/2}$, giving phase\n", - "$\\varphi = 1/2 = 0.5$. Verify that QPE recovers this phase with 3 phase bits." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "b0c1d2e3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above and fill in the missing parts!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: QPE on the Z gate\n", - "// Z|1> = -|1> = e^(i*pi)|1> = e^(2*pi*i * 1/2)|1>\n", - "// So the phase should be 0.5.\n", - "//\n", - "// TODO: Run QPE on the Z gate with:\n", - "// - Unitary: gate.Z\n", - "// - NumPhaseBits: 3\n", - "// - EigenState: a circuit that prepares |1> (apply X to qubit 0)\n", - "// - Shots: 1000\n", - "// Then verify that result.Phase is approximately 0.5.\n", - "//\n", - "// Hint: Use qpe.Run(ctx, qpe.Config{...})\n", - "\n", - "// ctx := context.Background()\n", - "//\n", - "// eigenCircuit, _ := builder.New(\"eigen\", 1).X(0).Build()\n", - "//\n", - "// result, err := qpe.Run(ctx, qpe.Config{\n", - "// \tUnitary: gate.Z,\n", - "// \tNumPhaseBits: 3,\n", - "// \tEigenState: eigenCircuit,\n", - "// \tShots: 1000,\n", - "// })\n", - "// if err != nil {\n", - "// \tpanic(err)\n", - "// }\n", - "//\n", - "// fmt.Printf(\"Z gate -- Estimated phase: %.4f (expected: 0.5000)\\n\", result.Phase)\n", - "// fmt.Printf(\"Match: %v\\n\", math.Abs(result.Phase-0.5) < 0.01)\n", - "// fmt.Println(\"Counts:\", result.Counts)\n", - "//\n", - "// svg := viz.Histogram(result.Counts, viz.WithTitle(\"Exercise 1: QPE on Z Gate\"))\n", - "// gonbui.DisplayHTML(svg)\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above and fill in the missing parts!\")" - ] - }, - { - "cell_type": "markdown", - "id": "c1d2e3f4", - "metadata": {}, - "source": [ - "### Exercise 2 -- QPE with Higher Precision\n", - "\n", - "Run QPE on the T gate with **5 phase bits** instead of 3. Because the true\n", - "phase $1/8 = 0.00100$ in binary is exactly representable with 3 bits,\n", - "increasing to 5 bits should still produce a sharp peak at the same value.\n", - "Compare the measurement distributions." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "d2e3f4a5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above and fill in the missing parts!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: QPE on T gate with higher precision (5 phase bits)\n", - "//\n", - "// TODO: Run QPE on the T gate with both 3 and 5 phase bits,\n", - "// then compare the results.\n", - "//\n", - "// Steps:\n", - "// 1. Create an eigenstate circuit: builder.New(\"eigen\", 1).X(0).Build()\n", - "// 2. Run qpe.Run with NumPhaseBits: 3\n", - "// 3. Run qpe.Run with NumPhaseBits: 5\n", - "// 4. Print both phases and compare with expected value 0.125\n", - "//\n", - "// Hint: The T gate phase 1/8 is exactly representable in 3 bits,\n", - "// so both should give the same sharp peak.\n", - "\n", - "// ctx := context.Background()\n", - "//\n", - "// eigenCircuit, _ := builder.New(\"eigen\", 1).X(0).Build()\n", - "//\n", - "// // TODO: Run QPE with 3 phase bits\n", - "// // result3, _ := qpe.Run(ctx, qpe.Config{\n", - "// // \tUnitary: gate.T,\n", - "// // \tNumPhaseBits: 3,\n", - "// // \tEigenState: eigenCircuit,\n", - "// // \tShots: 1000,\n", - "// // })\n", - "//\n", - "// // TODO: Run QPE with 5 phase bits\n", - "// // result5, _ := qpe.Run(ctx, qpe.Config{\n", - "// // \tUnitary: gate.T,\n", - "// // \tNumPhaseBits: 5,\n", - "// // \tEigenState: eigenCircuit,\n", - "// // \tShots: 1000,\n", - "// // })\n", - "//\n", - "// // fmt.Printf(\"3 phase bits -- phase: %.6f\\n\", result3.Phase)\n", - "// // fmt.Printf(\"5 phase bits -- phase: %.6f\\n\", result5.Phase)\n", - "// // fmt.Printf(\"Expected: 0.125000\\n\")\n", - "//\n", - "// // svg3 := viz.Histogram(result3.Counts, viz.WithTitle(\"QPE T Gate: 3 Phase Bits\"))\n", - "// // svg5 := viz.Histogram(result5.Counts, viz.WithTitle(\"QPE T Gate: 5 Phase Bits\"))\n", - "// // gonbui.DisplayHTML(svg3)\n", - "// // gonbui.DisplayHTML(svg5)\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above and fill in the missing parts!\")" - ] - }, - { - "cell_type": "markdown", - "id": "e3f4a5b6", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "\n", - "1. **Quantum Phase Estimation** extracts the eigenvalue phase of a unitary\n", - " operator using the inverse QFT. With $t$ ancilla qubits, it achieves\n", - " $t$ bits of precision. QPE is the key subroutine in Shor's algorithm,\n", - " quantum chemistry, and the HHL algorithm.\n", - "\n", - "2. **State preparation** via `gate.MustStatePrep` uses Mottonen decomposition\n", - " to initialize arbitrary quantum states from amplitude vectors.\n", - "\n", - "3. The **controlled-$U^{2^k}$** gates transfer phase information from the\n", - " unitary's eigenvalues into the phase register, where the inverse QFT\n", - " converts it to a readable binary fraction.\n", - "\n", - "4. When the true phase is exactly representable with $t$ bits, QPE recovers\n", - " it perfectly. Otherwise, the result is a distribution peaked near the\n", - " true phase, with precision improving exponentially with more phase bits.\n", - "\n", - "For textbook oracle algorithms (Deutsch-Jozsa, Bernstein-Vazirani, Simon's), see [Notebook 08b](08b-textbook-algorithms.ipynb).\n", - "\n", - "---\n", - "\n", - "**Next up:** Notebook 09 -- Grover's Search Algorithm, where we'll harness amplitude amplification for unstructured search." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/08b-textbook-algorithms.ipynb b/notebooks/08b-textbook-algorithms.ipynb deleted file mode 100644 index 01b6d85..0000000 --- a/notebooks/08b-textbook-algorithms.ipynb +++ /dev/null @@ -1,954 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "tb-intro-01", - "metadata": {}, - "source": [ - "# Notebook 08b -- Textbook Oracle Algorithms\n", - "\n", - "**Prerequisites:** Notebooks 01-07. Familiarity with superposition, measurement, and the oracle model.\n", - "\n", - "The earliest quantum algorithms were designed not to solve practical problems,\n", - "but to prove that quantum computers can outperform classical ones in\n", - "well-defined settings. These **textbook algorithms** operate in the **oracle\n", - "model**: they treat a function $f$ as a black box and ask how many queries\n", - "are needed to learn some property of $f$.\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Explain** how Deutsch-Jozsa distinguishes constant from balanced oracles in one query.\n", - "2. **Implement** Bernstein-Vazirani to recover a hidden bitstring.\n", - "3. **Describe** Simon's algorithm and how it finds hidden periods with $O(n)$ queries.\n", - "4. **Compare** the quantum query complexity of each algorithm to its classical counterpart.\n", - "\n", - "All three algorithms share the same **H-Oracle-H sandwich** pattern:\n", - "superposition, oracle query, interference, measure. The key difference\n", - "is what property of $f$ each algorithm extracts." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "tb-imports-01", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"context\"\n", - "\t\"fmt\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/algorithm/textbook\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/sim/statevector\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "tb-dj-explain-01", - "metadata": {}, - "source": [ - "## The Deutsch-Jozsa Algorithm\n", - "\n", - "**Problem:** Given a black-box function $f: \\{0,1\\}^n \\to \\{0,1\\}$ that is\n", - "promised to be either **constant** (same output for all inputs) or **balanced**\n", - "(outputs 0 for exactly half the inputs and 1 for the other half), determine\n", - "which type it is.\n", - "\n", - "**Classical lower bound:** Any deterministic classical algorithm needs\n", - "$2^{n-1} + 1$ queries in the worst case -- it must check just over half the\n", - "inputs before it can be certain.\n", - "\n", - "**Quantum solution:** The Deutsch-Jozsa algorithm answers with **a single\n", - "query** by exploiting interference:\n", - "\n", - "1. Prepare all input qubits in superposition with H gates.\n", - "2. Apply the oracle once.\n", - "3. Apply H gates again and measure.\n", - "\n", - "If $f$ is constant, all input qubits measure $|0\\rangle$ (constructive\n", - "interference on the all-zeros outcome). If $f$ is balanced, at least one\n", - "qubit measures $|1\\rangle$ (destructive interference eliminates the all-zeros\n", - "outcome)." - ] - }, - { - "cell_type": "markdown", - "id": "tb-dj-manual-explain", - "metadata": {}, - "source": [ - "### Building Deutsch-Jozsa from scratch\n", - "\n", - "The circuit has three stages:\n", - "1. **Superposition**: Apply H to all input qubits and X+H to the ancilla\n", - "2. **Oracle**: Apply the black-box function\n", - "3. **Interference**: Apply H to all input qubits and measure\n", - "\n", - "This H-Oracle-H sandwich is the core pattern shared by Deutsch-Jozsa,\n", - "Bernstein-Vazirani, and Simon's algorithm." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "tb-dj-manual-code", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Manual Deutsch-Jozsa circuit:\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "q3\n", - "\n", - "X\n", - "H\n", - "H\n", - "H\n", - "H\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "H\n", - "H\n", - "H\n", - "M\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Measurement counts: map[0101:480 1101:520]\n", - "All-zeros outcome present: false (expected: false for balanced)\n" - ] - } - ], - "source": [ - "%%\n", - "// Build Deutsch-Jozsa from scratch for a balanced oracle (mask=101)\n", - "b := builder.New(\"dj-manual\", 4).WithClbits(3) // 3 input + 1 ancilla, 3 classical bits\n", - "\n", - "// Step 1: Prepare ancilla in |-> and inputs in |+>\n", - "b.X(3).H(3) // ancilla in |->\n", - "b.H(0).H(1).H(2) // inputs in superposition\n", - "\n", - "// Step 2: Oracle — balanced with mask 101 (CNOT from inputs 0,2 to ancilla)\n", - "b.CNOT(0, 3).CNOT(2, 3)\n", - "\n", - "// Step 3: Interference — H on input qubits and measure\n", - "b.H(0).H(1).H(2)\n", - "b.Measure(0, 0).Measure(1, 1).Measure(2, 2)\n", - "\n", - "c, _ := b.Build()\n", - "fmt.Println(\"Manual Deutsch-Jozsa circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "\n", - "// Run and check\n", - "sim := statevector.New(4)\n", - "counts, _ := sim.Run(c, 1000)\n", - "fmt.Println(\"\\nMeasurement counts:\", counts)\n", - "\n", - "// For balanced oracle, the all-zeros outcome |000> should be absent\n", - "_, hasAllZeros := counts[\"000\"]\n", - "fmt.Printf(\"All-zeros outcome present: %v (expected: false for balanced)\\n\", hasAllZeros)" - ] - }, - { - "cell_type": "markdown", - "id": "tb-dj-sdk-transition", - "metadata": {}, - "source": [ - "Now let's see the same algorithm via Goqu's built-in `textbook` package, which handles all this construction for you:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "tb-dj-constant-01", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== Constant Oracle (f(x) = 0) ===\n", - "Circuit:\n", - "Measurement counts: map[000:1000]\n", - "Is constant: true\n", - "\n", - "All qubits measure 0 -- constructive interference on |000>.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "q3\n", - "\n", - "X\n", - "H\n", - "H\n", - "H\n", - "H\n", - "H\n", - "H\n", - "H\n", - "M\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Run Deutsch-Jozsa with a constant oracle (f(x) = 0 for all x)\n", - "result, err := textbook.DeutschJozsa(ctx, textbook.DJConfig{\n", - "\tNumQubits: 3,\n", - "\tOracle: textbook.ConstantOracle(0),\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"=== Constant Oracle (f(x) = 0) ===\")\n", - "fmt.Println(\"Circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(result.Circuit))\n", - "fmt.Println(\"Measurement counts:\", result.Counts)\n", - "fmt.Printf(\"Is constant: %v\\n\", result.IsConstant)\n", - "fmt.Println(\"\\nAll qubits measure 0 -- constructive interference on |000>.\")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "tb-dj-balanced-01", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== Balanced Oracle (mask = 101) ===\n", - "Measurement counts: map[101:1000]\n", - "Is constant: false\n", - "\n", - "The all-zeros outcome is absent -- the algorithm detects 'balanced'.\n" - ] - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Run Deutsch-Jozsa with a balanced oracle (mask = 0b101)\n", - "// This oracle computes f(x) = popcount(x AND 101) mod 2\n", - "result, err := textbook.DeutschJozsa(ctx, textbook.DJConfig{\n", - "\tNumQubits: 3,\n", - "\tOracle: textbook.BalancedOracle(0b101),\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"=== Balanced Oracle (mask = 101) ===\")\n", - "fmt.Println(\"Measurement counts:\", result.Counts)\n", - "fmt.Printf(\"Is constant: %v\\n\", result.IsConstant)\n", - "fmt.Println(\"\\nThe all-zeros outcome is absent -- the algorithm detects 'balanced'.\")" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "tb-dj-histogram-01", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "Deutsch-Jozsa: Constant Oracle\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "200\n", - "\n", - "400\n", - "\n", - "600\n", - "\n", - "800\n", - "\n", - "1000\n", - "\n", - "000\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "Deutsch-Jozsa: Balanced Oracle\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "200\n", - "\n", - "400\n", - "\n", - "600\n", - "\n", - "800\n", - "\n", - "1000\n", - "\n", - "101\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Histogram comparing constant vs balanced results\n", - "constResult, _ := textbook.DeutschJozsa(ctx, textbook.DJConfig{\n", - "\tNumQubits: 3,\n", - "\tOracle: textbook.ConstantOracle(0),\n", - "\tShots: 1000,\n", - "})\n", - "balResult, _ := textbook.DeutschJozsa(ctx, textbook.DJConfig{\n", - "\tNumQubits: 3,\n", - "\tOracle: textbook.BalancedOracle(0b101),\n", - "\tShots: 1000,\n", - "})\n", - "\n", - "svgConst := viz.Histogram(constResult.Counts, viz.WithTitle(\"Deutsch-Jozsa: Constant Oracle\"))\n", - "svgBal := viz.Histogram(balResult.Counts, viz.WithTitle(\"Deutsch-Jozsa: Balanced Oracle\"))\n", - "gonbui.DisplayHTML(svgConst)\n", - "gonbui.DisplayHTML(svgBal)" - ] - }, - { - "cell_type": "markdown", - "id": "tb-bv-explain-01", - "metadata": {}, - "source": [ - "## The Bernstein-Vazirani Algorithm\n", - "\n", - "**Problem:** Given an oracle for $f(x) = s \\cdot x \\mod 2$ (the dot product\n", - "of a hidden string $s$ with the input $x$, modulo 2), find $s$.\n", - "\n", - "**Classical lower bound:** A classical algorithm needs $n$ queries -- one for\n", - "each bit of $s$ (query with $x = e_i$ to learn $s_i$).\n", - "\n", - "**Quantum solution:** Bernstein-Vazirani recovers the entire $n$-bit secret\n", - "in **a single query**. The circuit is identical to Deutsch-Jozsa (Hadamard,\n", - "oracle, Hadamard, measure), but the measurement outcome directly encodes\n", - "the secret string $s$.\n", - "\n", - "The key insight: the Hadamard transform converts the phase kickback from\n", - "the oracle into a bitstring in the computational basis." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "tb-bv-demo-01", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== Bernstein-Vazirani ===\n", - "Circuit:\n", - "Secret found: 1011 (decimal 11)\n", - "Measurement counts: map[1011:1000]\n", - "\n", - "The secret string 1011 is recovered in a single query!\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "q3\n", - "\n", - "q4\n", - "\n", - "X\n", - "H\n", - "H\n", - "H\n", - "H\n", - "H\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "H\n", - "H\n", - "H\n", - "H\n", - "M\n", - "M\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Run Bernstein-Vazirani to find the secret string s = 1011 (decimal 11)\n", - "result, err := textbook.BernsteinVazirani(ctx, textbook.BVConfig{\n", - "\tNumQubits: 4,\n", - "\tSecret: 0b1011, // secret = \"1011\"\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"=== Bernstein-Vazirani ===\")\n", - "fmt.Println(\"Circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(result.Circuit))\n", - "fmt.Printf(\"Secret found: %04b (decimal %d)\\n\", result.Secret, result.Secret)\n", - "fmt.Println(\"Measurement counts:\", result.Counts)\n", - "fmt.Println(\"\\nThe secret string 1011 is recovered in a single query!\")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "tb-bv-histogram-01", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "Bernstein-Vazirani: Secret = 1011\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "200\n", - "\n", - "400\n", - "\n", - "600\n", - "\n", - "800\n", - "\n", - "1000\n", - "\n", - "1011\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "result, _ := textbook.BernsteinVazirani(ctx, textbook.BVConfig{\n", - "\tNumQubits: 4,\n", - "\tSecret: 0b1011,\n", - "\tShots: 1000,\n", - "})\n", - "\n", - "svg := viz.Histogram(result.Counts, viz.WithTitle(\"Bernstein-Vazirani: Secret = 1011\"))\n", - "gonbui.DisplayHTML(svg)" - ] - }, - { - "cell_type": "markdown", - "id": "tb-simon-explain-01", - "metadata": {}, - "source": [ - "## Simon's Algorithm\n", - "\n", - "**Problem:** Given an oracle for $f: \\{0,1\\}^n \\to \\{0,1\\}^n$ with the\n", - "promise that there exists a secret period $s$ such that $f(x) = f(y)$ if and\n", - "only if $x = y$ or $x = y \\oplus s$, find $s$.\n", - "\n", - "**Classical lower bound:** Any classical algorithm requires $\\Omega(2^{n/2})$\n", - "queries (birthday paradox bound).\n", - "\n", - "**Quantum solution:** Simon's algorithm needs only $O(n)$ quantum queries.\n", - "Each query produces a random value $y$ satisfying $y \\cdot s = 0 \\mod 2$.\n", - "After collecting $n-1$ linearly independent equations over GF(2), Gaussian\n", - "elimination recovers $s$.\n", - "\n", - "Simon's algorithm is historically significant as the inspiration for Shor's\n", - "algorithm -- both use the same pattern of applying a function in superposition\n", - "and then performing a Fourier-like transform to extract hidden structure." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "tb-simon-demo-01", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== Simon's Algorithm ===\n", - "Circuit (last round):\n", - "Recovered period: 110 (decimal 6)\n", - "Equations collected: 3\n", - " y_0 = 110 (y . s = 0 mod 2)\n", - " y_1 = 111 (y . s = 0 mod 2)\n", - " y_2 = 001 (y . s = 0 mod 2)\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "q3\n", - "\n", - "q4\n", - "\n", - "q5\n", - "\n", - "H\n", - "H\n", - "H\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "H\n", - "H\n", - "H\n", - "M\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Run Simon's algorithm with secret period s = 110 (decimal 6)\n", - "result, err := textbook.Simon(ctx, textbook.SimonConfig{\n", - "\tNumQubits: 3,\n", - "\tOracle: textbook.TwoToOneOracle(0b110, 3),\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"=== Simon's Algorithm ===\")\n", - "fmt.Println(\"Circuit (last round):\")\n", - "gonbui.DisplayHTML(draw.SVG(result.Circuit))\n", - "fmt.Printf(\"Recovered period: %03b (decimal %d)\\n\", result.Period, result.Period)\n", - "fmt.Printf(\"Equations collected: %d\\n\", len(result.Equations))\n", - "for i, eq := range result.Equations {\n", - "\tfmt.Printf(\" y_%d = %03b (y . s = 0 mod 2)\\n\", i, eq)\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "tb-predict-01", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** If we use a 6-qubit Bernstein-Vazirani oracle with secret $s = 101010$ (decimal 42), what measurement outcome should we see?\n", - "\n", - "**Pause and predict** before running the next cell.\n", - "\n", - "*Hint: The Bernstein-Vazirani algorithm uses a single query to the oracle. Think about the H-Oracle-H pattern and what it reveals about $s$.*" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "tb-predict-verify-01", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Secret found: 101010 (decimal 42)\n", - "Match: true\n", - "Counts: map[101010:1000]\n", - "\n", - "Prediction confirmed: all shots yield 101010.\n" - ] - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Verify: BV with 6 qubits, secret = 101010 (42)\n", - "result, err := textbook.BernsteinVazirani(ctx, textbook.BVConfig{\n", - "\tNumQubits: 6,\n", - "\tSecret: 0b101010,\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"Secret found: %06b (decimal %d)\\n\", result.Secret, result.Secret)\n", - "fmt.Printf(\"Match: %v\\n\", result.Secret == 42)\n", - "fmt.Println(\"Counts:\", result.Counts)\n", - "fmt.Println(\"\\nPrediction confirmed: all shots yield 101010.\")" - ] - }, - { - "cell_type": "markdown", - "id": "5su3ygrsiu4", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. Why does Deutsch-Jozsa need only 1 query while a classical deterministic algorithm needs 2^(n-1)+1?\n", - "2. In Bernstein-Vazirani, why does the measurement outcome directly reveal the secret string rather than some encoded version of it?\n", - "3. How does Simon's algorithm achieve an exponential speedup even though each quantum query only produces one linear equation?" - ] - }, - { - "cell_type": "markdown", - "id": "tb-exercises-header", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## Exercises\n", - "\n", - "### Exercise 1 -- Bernstein-Vazirani with a Different Secret\n", - "\n", - "Run the Bernstein-Vazirani algorithm with 5 qubits and secret $s = 10110$\n", - "(decimal 22). Verify that the algorithm recovers the secret in a single query." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "tb-exercise-01", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above and fill in the missing parts!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: BV with 5 qubits, secret = 10110 (22)\n", - "//\n", - "// TODO: Run the Bernstein-Vazirani algorithm with:\n", - "// - NumQubits: 5\n", - "// - Secret: 0b10110 (decimal 22)\n", - "// - Shots: 1000\n", - "// Then verify that result.Secret == 22.\n", - "//\n", - "// Hint: Use textbook.BernsteinVazirani(ctx, textbook.BVConfig{...})\n", - "\n", - "// ctx := context.Background()\n", - "//\n", - "// result, err := textbook.BernsteinVazirani(ctx, textbook.BVConfig{\n", - "// \tNumQubits: 5,\n", - "// \tSecret: 0b10110, // TODO: set the secret\n", - "// \tShots: 1000,\n", - "// })\n", - "// if err != nil {\n", - "// \tpanic(err)\n", - "// }\n", - "//\n", - "// fmt.Printf(\"Secret found: %05b (decimal %d)\\n\", result.Secret, result.Secret)\n", - "// fmt.Printf(\"Correct: %v\\n\", result.Secret == 22)\n", - "// fmt.Println(\"Counts:\", result.Counts)\n", - "//\n", - "// svg := viz.Histogram(result.Counts, viz.WithTitle(\"Exercise 1: BV Secret = 10110\"))\n", - "// gonbui.DisplayHTML(svg)\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above and fill in the missing parts!\")" - ] - }, - { - "cell_type": "markdown", - "id": "tb-exercise-02-header", - "metadata": {}, - "source": [ - "### Exercise 2 -- Deutsch-Jozsa with More Qubits\n", - "\n", - "Run Deutsch-Jozsa with 5 input qubits using a balanced oracle with mask\n", - "$11010$ (decimal 26). Verify that the all-zeros outcome is absent." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "tb-exercise-02", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above and fill in the missing parts!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: DJ with 5 qubits, balanced oracle mask = 11010 (26)\n", - "//\n", - "// TODO: Run the Deutsch-Jozsa algorithm with:\n", - "// - NumQubits: 5\n", - "// - Oracle: textbook.BalancedOracle(0b11010)\n", - "// - Shots: 1000\n", - "// Then verify that result.IsConstant == false.\n", - "//\n", - "// Hint: Use textbook.DeutschJozsa(ctx, textbook.DJConfig{...})\n", - "\n", - "// ctx := context.Background()\n", - "//\n", - "// result, err := textbook.DeutschJozsa(ctx, textbook.DJConfig{\n", - "// \tNumQubits: 5,\n", - "// \tOracle: textbook.BalancedOracle(0b11010),\n", - "// \tShots: 1000,\n", - "// })\n", - "// if err != nil {\n", - "// \tpanic(err)\n", - "// }\n", - "//\n", - "// fmt.Printf(\"Is constant: %v (expected: false)\\n\", result.IsConstant)\n", - "// fmt.Println(\"Counts:\", result.Counts)\n", - "//\n", - "// svg := viz.Histogram(result.Counts, viz.WithTitle(\"Exercise 2: DJ Balanced (mask=11010)\"))\n", - "// gonbui.DisplayHTML(svg)\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above and fill in the missing parts!\")" - ] - }, - { - "cell_type": "markdown", - "id": "tb-exercise-03-header", - "metadata": {}, - "source": [ - "### Exercise 3 -- Simon's Algorithm with a Different Period\n", - "\n", - "Run Simon's algorithm with 4 qubits and secret period $s = 1001$ (decimal 9).\n", - "Verify that the algorithm recovers the correct period." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "tb-exercise-03", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above and fill in the missing parts!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 3: Simon's algorithm with 4 qubits, period = 1001 (9)\n", - "//\n", - "// TODO: Run Simon's algorithm with:\n", - "// - NumQubits: 4\n", - "// - Oracle: textbook.TwoToOneOracle(0b1001, 4)\n", - "// - Shots: 1000\n", - "// Then verify that result.Period == 9.\n", - "//\n", - "// Hint: Use textbook.Simon(ctx, textbook.SimonConfig{...})\n", - "\n", - "// ctx := context.Background()\n", - "//\n", - "// result, err := textbook.Simon(ctx, textbook.SimonConfig{\n", - "// \tNumQubits: 4,\n", - "// \tOracle: textbook.TwoToOneOracle(0b1001, 4),\n", - "// \tShots: 1000,\n", - "// })\n", - "// if err != nil {\n", - "// \tpanic(err)\n", - "// }\n", - "//\n", - "// fmt.Printf(\"Recovered period: %04b (decimal %d)\\n\", result.Period, result.Period)\n", - "// fmt.Printf(\"Correct: %v\\n\", result.Period == 9)\n", - "// fmt.Printf(\"Equations collected: %d\\n\", len(result.Equations))\n", - "// for i, eq := range result.Equations {\n", - "// \tfmt.Printf(\" y_%d = %04b\\n\", i, eq)\n", - "// }\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above and fill in the missing parts!\")" - ] - }, - { - "cell_type": "markdown", - "id": "tb-takeaways-01", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "\n", - "1. **Oracle model algorithms** demonstrate quantum speedups by treating\n", - " functions as black boxes. The speedup is provable because we can count\n", - " queries exactly.\n", - "\n", - "2. **Deutsch-Jozsa** achieves an exponential query speedup: 1 quantum query\n", - " vs. $2^{n-1}+1$ classical queries to distinguish constant from balanced.\n", - "\n", - "3. **Bernstein-Vazirani** achieves a linear query speedup: 1 quantum query\n", - " vs. $n$ classical queries to recover a hidden bitstring.\n", - "\n", - "4. **Simon's algorithm** achieves an exponential speedup: $O(n)$ quantum\n", - " queries vs. $\\Omega(2^{n/2})$ classical queries to find a hidden period.\n", - " It directly inspired Shor's factoring algorithm.\n", - "\n", - "5. All three algorithms share the **H-Oracle-H** pattern: prepare a\n", - " superposition, apply the oracle, interfere, and measure. The quantum\n", - " advantage comes from extracting global properties of $f$ that are\n", - " encoded in the relative phases of the superposition.\n", - "\n", - "---\n", - "\n", - "**Next up:** Notebook 09 -- Grover's Search Algorithm, where we'll harness amplitude amplification for unstructured search." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/09-grovers-search.ipynb b/notebooks/09-grovers-search.ipynb deleted file mode 100644 index ce091b8..0000000 --- a/notebooks/09-grovers-search.ipynb +++ /dev/null @@ -1,1321 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a1b2c3d4", - "metadata": {}, - "source": [ - "# Notebook 09 -- Grover's Search Algorithm\n", - "\n", - "**Prerequisites:** Notebooks 01-06. Familiarity with multi-qubit gates and superposition.\n", - "\n", - "Classical computers searching an unsorted database of $N$ items need $O(N)$\n", - "queries in the worst case -- there is no shortcut when there is no structure\n", - "to exploit. Grover's algorithm (1996) achieves a **quadratic speedup**,\n", - "finding a marked item in only $O(\\sqrt{N})$ queries.\n", - "\n", - "This is provably optimal for unstructured search: no quantum algorithm can\n", - "do better than $\\Omega(\\sqrt{N})$. While not an exponential speedup like\n", - "Shor's factoring algorithm, Grover's speedup is universal -- it applies to\n", - "**any** search problem where we can check whether a candidate is a solution.\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Describe** Grover's algorithm and its quadratic speedup for unstructured search.\n", - "2. **Implement** phase and boolean oracles for marking target states.\n", - "3. **Predict** the optimal number of Grover iterations for a given search space.\n", - "4. **Explain** why too many iterations causes over-rotation and reduces success probability.\n", - "\n", - "In this notebook we will:\n", - "\n", - "1. Implement Grover's algorithm using the Goqu SDK.\n", - "2. Understand the **oracle** and **diffusion operator**.\n", - "3. Explore the geometric picture of **amplitude amplification**.\n", - "4. See what happens when we iterate too many times (**over-rotation**).\n", - "5. Search for multiple solutions simultaneously." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "b2c3d4e5", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"context\"\n", - "\t\"fmt\"\n", - "\t\"math\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/algorithm/grover\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/sim/statevector\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "c3d4e5f6", - "metadata": {}, - "source": [ - "## The Problem: Searching Without Structure\n", - "\n", - "Imagine you have a phone book with $N = 2^n$ entries, but the entries are\n", - "in random order. You want to find the entry matching a specific criterion.\n", - "Classically, you must check entries one by one -- on average $N/2$ queries.\n", - "\n", - "In the quantum setting, we encode the search space into $n$ qubits, giving\n", - "us $N = 2^n$ computational basis states. A **quantum oracle** marks the\n", - "target state(s) by flipping their phase. Grover's algorithm then amplifies\n", - "the amplitude of the marked states until they dominate the measurement\n", - "outcome.\n", - "\n", - "| Qubits ($n$) | Search space ($N$) | Classical queries | Grover queries |\n", - "|:---:|:---:|:---:|:---:|\n", - "| 3 | 8 | 4 | 2 |\n", - "| 4 | 16 | 8 | 3 |\n", - "| 10 | 1024 | 512 | 25 |\n", - "| 20 | 1,048,576 | 524,288 | 804 |" - ] - }, - { - "cell_type": "markdown", - "id": "d4e5f6a7", - "metadata": {}, - "source": [ - "## How Grover's Algorithm Works\n", - "\n", - "Grover's algorithm repeats two operations in a loop:\n", - "\n", - "1. **Oracle** $O$: Flips the phase of the target state(s). If $|x\\rangle$\n", - " is a target, $O|x\\rangle = -|x\\rangle$; otherwise, $O|x\\rangle = |x\\rangle$.\n", - "\n", - "2. **Diffusion operator** $D = 2|s\\rangle\\langle s| - I$: Reflects all\n", - " amplitudes about their mean, where $|s\\rangle = H^{\\otimes n}|0\\rangle$\n", - " is the uniform superposition.\n", - "\n", - "### Geometric Picture\n", - "\n", - "The state lives in a 2D plane spanned by the target state $|t\\rangle$\n", - "and the uniform superposition of non-target states $|t^\\perp\\rangle$.\n", - "Each Grover iteration rotates the state vector by angle\n", - "$2\\theta$ toward $|t\\rangle$, where $\\sin\\theta = \\sqrt{M/N}$\n", - "($M$ = number of solutions).\n", - "\n", - "The initial state $|s\\rangle$ has a small angle $\\theta$ to\n", - "$|t^\\perp\\rangle$. After $k$ iterations the angle becomes\n", - "$(2k+1)\\theta$. We want this to be as close to $\\pi/2$ as possible\n", - "(perfectly aligned with $|t\\rangle$), giving the optimal iteration count:\n", - "\n", - "$$k_{\\text{opt}} = \\left\\lfloor \\frac{\\pi}{4} \\sqrt{\\frac{N}{M}} \\right\\rfloor$$" - ] - }, - { - "cell_type": "markdown", - "id": "kb9fjaoyf6h", - "metadata": {}, - "source": [ - "### Misconception: \"Quantum computers try all answers simultaneously\"\n", - "\n", - "The most common misconception about quantum computing is that a quantum\n", - "computer \"tries all $N$ answers at once\" and instantly finds the right one.\n", - "If this were true, Grover's algorithm would need just **1 step** -- but it\n", - "actually needs $O(\\sqrt{N})$ steps, a quadratic speedup, not an exponential one.\n", - "\n", - "The quantum advantage comes from **interference**, not from parallel evaluation.\n", - "After applying the oracle, all $N$ basis states still exist in superposition --\n", - "but the algorithm uses carefully designed interference (the diffusion operator)\n", - "to gradually **amplify** the probability of the correct answer and **suppress**\n", - "the wrong ones. This amplification takes $\\sqrt{N}$ rounds to complete.\n", - "\n", - "This is also why \"quantum parallelism\" is a misleading term. A quantum computer\n", - "with $n$ qubits does not have $2^n$ parallel processors. It has $2^n$\n", - "probability amplitudes that can interfere with each other -- and harnessing\n", - "that interference for speedup requires clever algorithm design." - ] - }, - { - "cell_type": "markdown", - "id": "gvzbn6b8gjg", - "metadata": {}, - "source": [ - "### Building Grover's algorithm from scratch\n", - "\n", - "Before using the SDK, let's build one iteration manually on 3 qubits to\n", - "see exactly what happens. The two components are:\n", - "\n", - "1. **Oracle** -- flips the phase of the target state |101>\n", - "2. **Diffusion** -- reflects about the mean amplitude (H-X-CCZ-X-H)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "wdcjdyl3s5f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Manual Grover circuit (1 iteration):\n", - "\n", - "Counts: map[000:28 001:20 010:36 011:32 100:41 101:780 110:28 111:35]\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "H\n", - "H\n", - "H\n", - "X\n", - "\n", - "CCZ\n", - "CCZ\n", - "CCZ\n", - "X\n", - "H\n", - "H\n", - "H\n", - "X\n", - "X\n", - "X\n", - "\n", - "CCZ\n", - "CCZ\n", - "CCZ\n", - "X\n", - "X\n", - "X\n", - "H\n", - "H\n", - "H\n", - "M\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "Manual Grover: 1 iteration for |101>\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "200\n", - "\n", - "400\n", - "\n", - "600\n", - "\n", - "800\n", - "\n", - "000\n", - "\n", - "001\n", - "\n", - "010\n", - "\n", - "011\n", - "\n", - "100\n", - "\n", - "101\n", - "\n", - "110\n", - "\n", - "111\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Manual Grover on 3 qubits, searching for |101>\n", - "b := builder.New(\"grover-manual\", 3)\n", - "\n", - "// Step 1: Initialize uniform superposition\n", - "b.H(0).H(1).H(2)\n", - "\n", - "// === One Grover iteration ===\n", - "\n", - "// Step 2: Oracle — flip phase of |101> (q0=1, q1=0, q2=1)\n", - "// To mark |101>: apply X to q1 (flip the 0-bit), then CCZ, then X to q1\n", - "b.X(1)\n", - "b.CCZ(0, 1, 2)\n", - "b.X(1)\n", - "\n", - "// Step 3: Diffusion operator (reflect about |+...+>)\n", - "b.H(0).H(1).H(2)\n", - "b.X(0).X(1).X(2)\n", - "b.CCZ(0, 1, 2)\n", - "b.X(0).X(1).X(2)\n", - "b.H(0).H(1).H(2)\n", - "\n", - "// Measure\n", - "b.MeasureAll()\n", - "c, _ := b.Build()\n", - "\n", - "fmt.Println(\"Manual Grover circuit (1 iteration):\")\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "\n", - "sim := statevector.New(3)\n", - "counts, _ := sim.Run(c, 1000)\n", - "fmt.Println(\"\\nCounts:\", counts)\n", - "gonbui.DisplayHTML(viz.Histogram(counts, viz.WithTitle(\"Manual Grover: 1 iteration for |101>\")))" - ] - }, - { - "cell_type": "markdown", - "id": "cn0csdaakt", - "metadata": {}, - "source": [ - "The SDK's `grover.Run` handles oracle construction, optimal iteration count, and measurement for you:" - ] - }, - { - "cell_type": "markdown", - "id": "e5f6a7b8", - "metadata": {}, - "source": [ - "## Basic Grover Search\n", - "\n", - "Let's search for the state $|1010\\rangle$ (decimal 10) in a 4-qubit space\n", - "of $N = 16$ items. With 1 solution, the optimal iteration count is\n", - "$\\lfloor \\pi/4 \\cdot \\sqrt{16} \\rfloor = \\lfloor \\pi \\rfloor = 3$." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "f6a7b8c9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== Grover Search: 4 qubits, target |1010> ===\n", - "Top result: 1010\n", - "Iterations used: 3\n", - "Measurement counts: map[0000:1 0001:2 0010:3 0011:4 0100:3 0101:6 0110:1 0111:4 1000:6 1001:2 1010:954 1011:3 1100:1 1101:4 1110:2 1111:4]\n", - "Success probability: 95.4%\n" - ] - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Search for |1010> (decimal 10) in a 4-qubit search space\n", - "result, err := grover.Run(ctx, grover.Config{\n", - "\tNumQubits: 4,\n", - "\tOracle: grover.PhaseOracle([]int{0b1010}, 4),\n", - "\tNumSolutions: 1,\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"=== Grover Search: 4 qubits, target |1010> ===\")\n", - "fmt.Println(\"Top result:\", result.TopResult)\n", - "fmt.Println(\"Iterations used:\", result.NumIters)\n", - "fmt.Println(\"Measurement counts:\", result.Counts)\n", - "\n", - "// Calculate success probability\n", - "total := 0\n", - "for _, v := range result.Counts {\n", - "\ttotal += v\n", - "}\n", - "if count, ok := result.Counts[\"1010\"]; ok {\n", - "\tfmt.Printf(\"Success probability: %.1f%%\\n\", float64(count)/float64(total)*100)\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "a7b8c9d0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Circuit diagram:\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "q3\n", - "\n", - "H\n", - "H\n", - "H\n", - "H\n", - "X\n", - "X\n", - "\n", - "\n", - "\n", - "\n", - "Z\n", - "X\n", - "X\n", - "H\n", - "H\n", - "H\n", - "H\n", - "X\n", - "X\n", - "X\n", - "X\n", - "\n", - "\n", - "\n", - "\n", - "Z\n", - "X\n", - "X\n", - "X\n", - "X\n", - "H\n", - "H\n", - "H\n", - "H\n", - "X\n", - "X\n", - "\n", - "\n", - "\n", - "\n", - "Z\n", - "X\n", - "X\n", - "H\n", - "H\n", - "H\n", - "H\n", - "X\n", - "X\n", - "X\n", - "X\n", - "\n", - "\n", - "\n", - "\n", - "Z\n", - "X\n", - "X\n", - "X\n", - "X\n", - "H\n", - "H\n", - "H\n", - "H\n", - "X\n", - "X\n", - "\n", - "\n", - "\n", - "\n", - "Z\n", - "X\n", - "X\n", - "H\n", - "H\n", - "H\n", - "H\n", - "X\n", - "X\n", - "X\n", - "X\n", - "\n", - "\n", - "\n", - "\n", - "Z\n", - "X\n", - "X\n", - "X\n", - "X\n", - "H\n", - "H\n", - "H\n", - "H\n", - "M\n", - "M\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "Grover Search: target |1010>\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "200\n", - "\n", - "400\n", - "\n", - "600\n", - "\n", - "800\n", - "\n", - "1000\n", - "\n", - "0000\n", - "\n", - "0010\n", - "\n", - "0011\n", - "\n", - "0100\n", - "\n", - "0101\n", - "\n", - "0110\n", - "\n", - "0111\n", - "\n", - "1001\n", - "\n", - "1010\n", - "\n", - "1011\n", - "\n", - "1100\n", - "\n", - "1101\n", - "\n", - "1110\n", - "\n", - "1111\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "result, _ := grover.Run(ctx, grover.Config{\n", - "\tNumQubits: 4,\n", - "\tOracle: grover.PhaseOracle([]int{0b1010}, 4),\n", - "\tNumSolutions: 1,\n", - "\tShots: 1000,\n", - "})\n", - "\n", - "fmt.Println(\"Circuit diagram:\")\n", - "gonbui.DisplayHTML(draw.SVG(result.Circuit))\n", - "\n", - "svg := viz.Histogram(result.Counts, viz.WithTitle(\"Grover Search: target |1010>\"))\n", - "gonbui.DisplayHTML(svg)" - ] - }, - { - "cell_type": "markdown", - "id": "b8c9d0e1", - "metadata": {}, - "source": [ - "## Phase Oracle vs Boolean Oracle\n", - "\n", - "The Goqu SDK provides two ways to construct an oracle:\n", - "\n", - "**Phase Oracle** (`grover.PhaseOracle`): You specify the target states\n", - "directly as integer indices. This is the most efficient approach when you\n", - "know the targets ahead of time.\n", - "\n", - "**Boolean Oracle** (`grover.BooleanOracle`): You provide a classical\n", - "function $f(x) \\to \\text{bool}$ that returns `true` for target states.\n", - "The oracle internally evaluates $f$ on all inputs to find the targets.\n", - "This is more natural for constraint-satisfaction problems where the\n", - "\"target\" is defined by a property rather than an explicit list.\n", - "\n", - "Both produce the same phase-kickback effect: marked states get a $-1$\n", - "phase flip." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "c9d0e1f2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== Phase Oracle ===\n", - "Top result: 101\n", - "Counts: map[000:5 001:7 010:8 011:8 100:10 101:951 110:6 111:5]\n", - "\n", - "=== Boolean Oracle ===\n", - "Top result: 101\n", - "Counts: map[000:5 001:10 010:7 011:5 100:7 101:953 110:3 111:10]\n", - "\n", - "Both oracles find the same target -- they produce equivalent circuits.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "Phase Oracle: target |101>\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "200\n", - "\n", - "400\n", - "\n", - "600\n", - "\n", - "800\n", - "\n", - "1000\n", - "\n", - "000\n", - "\n", - "001\n", - "\n", - "010\n", - "\n", - "011\n", - "\n", - "100\n", - "\n", - "101\n", - "\n", - "110\n", - "\n", - "111\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "Boolean Oracle: target |101>\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "200\n", - "\n", - "400\n", - "\n", - "600\n", - "\n", - "800\n", - "\n", - "1000\n", - "\n", - "000\n", - "\n", - "001\n", - "\n", - "010\n", - "\n", - "011\n", - "\n", - "100\n", - "\n", - "101\n", - "\n", - "110\n", - "\n", - "111\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Phase oracle: directly specify target |101> = 5\n", - "phaseOracle := grover.PhaseOracle([]int{0b101}, 3)\n", - "\n", - "// Boolean oracle: define a function that returns true for target\n", - "boolOracle := grover.BooleanOracle(func(x int) bool {\n", - "\treturn x == 0b101\n", - "}, 3)\n", - "\n", - "// Run both and compare\n", - "resultPhase, _ := grover.Run(ctx, grover.Config{\n", - "\tNumQubits: 3,\n", - "\tOracle: phaseOracle,\n", - "\tNumSolutions: 1,\n", - "\tShots: 1000,\n", - "})\n", - "\n", - "resultBool, _ := grover.Run(ctx, grover.Config{\n", - "\tNumQubits: 3,\n", - "\tOracle: boolOracle,\n", - "\tNumSolutions: 1,\n", - "\tShots: 1000,\n", - "})\n", - "\n", - "fmt.Println(\"=== Phase Oracle ===\")\n", - "fmt.Println(\"Top result:\", resultPhase.TopResult)\n", - "fmt.Println(\"Counts:\", resultPhase.Counts)\n", - "\n", - "fmt.Println()\n", - "fmt.Println(\"=== Boolean Oracle ===\")\n", - "fmt.Println(\"Top result:\", resultBool.TopResult)\n", - "fmt.Println(\"Counts:\", resultBool.Counts)\n", - "\n", - "fmt.Println()\n", - "fmt.Println(\"Both oracles find the same target -- they produce equivalent circuits.\")\n", - "\n", - "svgPhase := viz.Histogram(resultPhase.Counts, viz.WithTitle(\"Phase Oracle: target |101>\"))\n", - "svgBool := viz.Histogram(resultBool.Counts, viz.WithTitle(\"Boolean Oracle: target |101>\"))\n", - "gonbui.DisplayHTML(svgPhase)\n", - "gonbui.DisplayHTML(svgBool)" - ] - }, - { - "cell_type": "markdown", - "id": "d0e1f2a3", - "metadata": {}, - "source": [ - "## Optimal Number of Iterations\n", - "\n", - "The magic of Grover's algorithm lies in the **amplitude amplification**\n", - "process. Each iteration rotates the state vector closer to the target\n", - "subspace. The optimal number of iterations is:\n", - "\n", - "$$k_{\\text{opt}} = \\left\\lfloor \\frac{\\pi}{4} \\sqrt{\\frac{N}{M}} \\right\\rfloor$$\n", - "\n", - "where $N = 2^n$ is the search space size and $M$ is the number of solutions.\n", - "\n", - "At the optimal iteration count, the probability of measuring a target state\n", - "is close to 1. With fewer or more iterations, the probability is lower.\n", - "\n", - "Let's visualize how the success probability changes with the number of\n", - "iterations for a 3-qubit search (N=8, M=1)." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "e1f2a3b4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Search space: N = 8, solutions: M = 1\n", - "Optimal iterations: floor(pi/4 * sqrt(8/1)) = 2\n", - "\n", - "Iterations: 1 | P(target) = 78.3%\n", - "Iterations: 2 | P(target) = 94.2% <-- optimal\n", - "Iterations: 3 | P(target) = 33.1%\n", - "Iterations: 4 | P(target) = 1.1%\n", - "Iterations: 5 | P(target) = 53.6%\n", - "Iterations: 6 | P(target) = 99.9%\n", - "\n", - "The success probability peaks at the optimal iteration count.\n", - "Too few iterations: not enough amplification.\n", - "Too many iterations: the state rotates past the target (over-rotation).\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "Success Count vs Iteration Count (3 qubits, 2000 shots)\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "500\n", - "\n", - "1000\n", - "\n", - "1500\n", - "\n", - "2000\n", - "\n", - "k=1\n", - "\n", - "k=2\n", - "\n", - "k=3\n", - "\n", - "k=4\n", - "\n", - "k=5\n", - "\n", - "k=6\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "n := 3\n", - "N := 1 << n // 8\n", - "M := 1\n", - "\n", - "optIter := int(math.Floor(math.Pi / 4 * math.Sqrt(float64(N)/float64(M))))\n", - "fmt.Printf(\"Search space: N = %d, solutions: M = %d\\n\", N, M)\n", - "fmt.Printf(\"Optimal iterations: floor(pi/4 * sqrt(%d/%d)) = %d\\n\\n\", N, M, optIter)\n", - "\n", - "// Run Grover with different iteration counts and track success probability\n", - "probCounts := make(map[string]int)\n", - "for iters := 1; iters <= 6; iters++ {\n", - "\tresult, _ := grover.Run(ctx, grover.Config{\n", - "\t\tNumQubits: n,\n", - "\t\tOracle: grover.PhaseOracle([]int{0b101}, n),\n", - "\t\tNumIters: iters,\n", - "\t\tShots: 2000,\n", - "\t})\n", - "\n", - "\t// Calculate success probability\n", - "\ttotal := 0\n", - "\tfor _, v := range result.Counts {\n", - "\t\ttotal += v\n", - "\t}\n", - "\tsuccesses := 0\n", - "\tif c, ok := result.Counts[\"101\"]; ok {\n", - "\t\tsuccesses = c\n", - "\t}\n", - "\tprob := float64(successes) / float64(total) * 100\n", - "\n", - "\tmarker := \"\"\n", - "\tif iters == optIter {\n", - "\t\tmarker = \" <-- optimal\"\n", - "\t}\n", - "\tfmt.Printf(\"Iterations: %d | P(target) = %5.1f%%%s\\n\", iters, prob, marker)\n", - "\n", - "\t// Build a histogram-friendly map: label = iteration count, value = successes\n", - "\tlabel := fmt.Sprintf(\"k=%d\", iters)\n", - "\tprobCounts[label] = successes\n", - "}\n", - "\n", - "fmt.Println(\"\\nThe success probability peaks at the optimal iteration count.\")\n", - "fmt.Println(\"Too few iterations: not enough amplification.\")\n", - "fmt.Println(\"Too many iterations: the state rotates past the target (over-rotation).\")\n", - "\n", - "svg := viz.Histogram(probCounts, viz.WithTitle(\"Success Count vs Iteration Count (3 qubits, 2000 shots)\"))\n", - "gonbui.DisplayHTML(svg)" - ] - }, - { - "cell_type": "markdown", - "id": "f2a3b4c5", - "metadata": {}, - "source": [ - "## Over-Rotation\n", - "\n", - "A crucial subtlety: Grover's algorithm is **not** \"the more iterations,\n", - "the better.\" Because the state rotates in a 2D plane, iterating past the\n", - "optimal point causes the state to **rotate away** from the target.\n", - "\n", - "After the optimal number of iterations, additional iterations decrease\n", - "the success probability. The state oscillates sinusoidally, periodically\n", - "returning to near-zero probability of finding the target.\n", - "\n", - "This is fundamentally different from classical search, where checking\n", - "more items never hurts. In Grover's algorithm, **knowing when to stop**\n", - "is essential." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "a3b4c5d6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== Over-Rotation Demo: 3 qubits, target |110> ===\n", - "\n", - "k= 1 : P(target) = 78.3% #######################################\n", - "k= 2 : P(target) = 94.3% ###############################################\n", - "k= 3 : P(target) = 31.4% ###############\n", - "k= 5 : P(target) = 52.9% ##########################\n", - "k= 7 : P(target) = 58.1% #############################\n", - "k=10 : P(target) = 92.7% ##############################################\n", - "\n", - "The probability oscillates! At k=2 it peaks near 95%,\n", - "but at k=5 it drops below 1%, then rises again around k=7-8.\n", - "This periodic behavior is a direct consequence of the rotation picture.\n" - ] - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Demonstrate over-rotation on 3 qubits with 10 iterations\n", - "// Optimal is 2 iterations; 10 is way past the sweet spot\n", - "fmt.Println(\"=== Over-Rotation Demo: 3 qubits, target |110> ===\")\n", - "fmt.Println()\n", - "\n", - "for _, iters := range []int{1, 2, 3, 5, 7, 10} {\n", - "\tresult, _ := grover.Run(ctx, grover.Config{\n", - "\t\tNumQubits: 3,\n", - "\t\tOracle: grover.PhaseOracle([]int{0b110}, 3),\n", - "\t\tNumIters: iters,\n", - "\t\tShots: 2000,\n", - "\t})\n", - "\n", - "\ttotal := 0\n", - "\tfor _, v := range result.Counts {\n", - "\t\ttotal += v\n", - "\t}\n", - "\tsuccesses := 0\n", - "\tif c, ok := result.Counts[\"110\"]; ok {\n", - "\t\tsuccesses = c\n", - "\t}\n", - "\tprob := float64(successes) / float64(total) * 100\n", - "\n", - "\tbar := \"\"\n", - "\tfor j := 0; j < int(prob/2); j++ {\n", - "\t\tbar += \"#\"\n", - "\t}\n", - "\tfmt.Printf(\"k=%2d : P(target) = %5.1f%% %s\\n\", iters, prob, bar)\n", - "}\n", - "\n", - "fmt.Println()\n", - "fmt.Println(\"The probability oscillates! At k=2 it peaks near 95%,\")\n", - "fmt.Println(\"but at k=5 it drops below 1%, then rises again around k=7-8.\")\n", - "fmt.Println(\"This periodic behavior is a direct consequence of the rotation picture.\")" - ] - }, - { - "cell_type": "markdown", - "id": "b4c5d6e7", - "metadata": {}, - "source": [ - "## Multiple Solutions\n", - "\n", - "Grover's algorithm generalizes naturally to searching for $M > 1$ marked\n", - "items. With more solutions, fewer iterations are needed because the target\n", - "subspace is larger (the initial overlap $\\sin\\theta = \\sqrt{M/N}$ is\n", - "bigger).\n", - "\n", - "The optimal iteration count becomes:\n", - "\n", - "$$k_{\\text{opt}} = \\left\\lfloor \\frac{\\pi}{4} \\sqrt{\\frac{N}{M}} \\right\\rfloor$$\n", - "\n", - "For example, with $N = 8$ and $M = 2$: $k_{\\text{opt}} = \\lfloor \\pi/4 \\cdot 2 \\rfloor = 1$.\n", - "\n", - "The measurement outcome will be one of the marked states, chosen\n", - "approximately uniformly at random." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "c5d6e7f8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== Multiple Solutions: targets |011> and |110> ===\n", - "Top result: 110\n", - "Iterations used: 1\n", - "Counts: map[011:991 110:1009]\n", - " |011> : 991 (49.5%)\n", - " |110> : 1009 (50.4%)\n", - "\n", - "Both targets are amplified roughly equally.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "Grover: Two Solutions |011> and |110>\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "200\n", - "\n", - "400\n", - "\n", - "600\n", - "\n", - "800\n", - "\n", - "1000\n", - "\n", - "1200\n", - "\n", - "011\n", - "\n", - "110\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Search for two targets: |011> = 3 and |110> = 6\n", - "result, err := grover.Run(ctx, grover.Config{\n", - "\tNumQubits: 3,\n", - "\tOracle: grover.PhaseOracle([]int{0b011, 0b110}, 3),\n", - "\tNumSolutions: 2,\n", - "\tShots: 2000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"=== Multiple Solutions: targets |011> and |110> ===\")\n", - "fmt.Println(\"Top result:\", result.TopResult)\n", - "fmt.Println(\"Iterations used:\", result.NumIters)\n", - "fmt.Println(\"Counts:\", result.Counts)\n", - "\n", - "// Show the counts for the two targets\n", - "total := 0\n", - "for _, v := range result.Counts {\n", - "\ttotal += v\n", - "}\n", - "for _, target := range []string{\"011\", \"110\"} {\n", - "\tif c, ok := result.Counts[target]; ok {\n", - "\t\tfmt.Printf(\" |%s> : %d (%.1f%%)\\n\", target, c, float64(c)/float64(total)*100)\n", - "\t}\n", - "}\n", - "\n", - "fmt.Println(\"\\nBoth targets are amplified roughly equally.\")\n", - "\n", - "svg := viz.Histogram(result.Counts, viz.WithTitle(\"Grover: Two Solutions |011> and |110>\"))\n", - "gonbui.DisplayHTML(svg)" - ] - }, - { - "cell_type": "markdown", - "id": "d6e7f8a9", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** How many Grover iterations are optimal for searching a 4-qubit space ($N = 16$) with 1 marked item? What happens if you iterate too many times?\n", - "\n", - "**Pause and predict** before running the next cell.\n", - "\n", - "*Hint: The optimal number of iterations is $k_{\\text{opt}} = \\lfloor \\frac{\\pi}{4}\\sqrt{N/M} \\rfloor$ where $M$ is the number of marked items. What is this for $N=16, M=1$?*" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "e7f8a9b0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Predicted optimal iterations: 3\n", - "Actual iterations used: 3\n", - "Match: true\n", - "\n", - "Top result: 1100\n", - "Counts: map[0000:4 0001:2 0010:5 0011:1 0100:1 0101:4 0110:6 0111:5 1000:1 1001:5 1010:5 1011:2 1100:953 1101:1 1110:2 1111:3]\n" - ] - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Let the algorithm choose the optimal iteration count (NumIters = 0)\n", - "result, err := grover.Run(ctx, grover.Config{\n", - "\tNumQubits: 4,\n", - "\tOracle: grover.PhaseOracle([]int{0b1100}, 4),\n", - "\tNumSolutions: 1,\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "expected := int(math.Floor(math.Pi / 4 * math.Sqrt(16.0/1.0)))\n", - "fmt.Printf(\"Predicted optimal iterations: %d\\n\", expected)\n", - "fmt.Printf(\"Actual iterations used: %d\\n\", result.NumIters)\n", - "fmt.Printf(\"Match: %v\\n\", result.NumIters == expected)\n", - "fmt.Println(\"\\nTop result:\", result.TopResult)\n", - "fmt.Println(\"Counts:\", result.Counts)" - ] - }, - { - "cell_type": "markdown", - "id": "g6754hdn1pn", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. What happens if you run too many Grover iterations past the optimal count?\n", - "2. How does the optimal iteration count change if there are M solutions instead of 1?\n", - "3. Why is Grover's quadratic speedup provably optimal for unstructured search?" - ] - }, - { - "cell_type": "markdown", - "id": "f8a9b0c1", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## Exercises\n", - "\n", - "### Exercise 1 -- Search for Multiple Solutions\n", - "\n", - "Use Grover's algorithm on 4 qubits to search for three targets simultaneously:\n", - "$|0011\\rangle$, $|0110\\rangle$, and $|1001\\rangle$.\n", - "\n", - "Before running the code, predict the optimal iteration count using the formula\n", - "$\\lfloor \\pi/4 \\cdot \\sqrt{16/3} \\rfloor$. Then verify by checking `result.NumIters`." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "a9b0c1d2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the config!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: Search for 3 targets in a 4-qubit space\n", - "// Targets: |0011>, |0110>, |1001>\n", - "//\n", - "// Step 1: Predict the optimal iteration count using the formula\n", - "// floor(pi/4 * sqrt(N/M)) where N=16, M=3\n", - "//\n", - "// Step 2: Run Grover's algorithm and verify your prediction\n", - "//\n", - "// TODO: Calculate and print the predicted iteration count\n", - "// predicted := int(math.Floor(math.Pi / 4 * math.Sqrt(???)))\n", - "// fmt.Printf(\"Predicted optimal iterations: %d\\n\\n\", predicted)\n", - "//\n", - "// TODO: Configure and run Grover's search for 3 targets\n", - "// result, err := grover.Run(ctx, grover.Config{\n", - "// NumQubits: ???,\n", - "// Oracle: grover.PhaseOracle([]int{???}, ???),\n", - "// NumSolutions: ???,\n", - "// Shots: 2000,\n", - "// })\n", - "//\n", - "// TODO: Print iterations used, top result, and counts\n", - "// TODO: Calculate and print the success probability for each target\n", - "//\n", - "// TODO: Visualize with viz.Histogram\n", - "_ = context.Background()\n", - "fmt.Println(\"Uncomment the code above and fill in the config!\")" - ] - }, - { - "cell_type": "markdown", - "id": "b0c1d2e3", - "metadata": {}, - "source": [ - "### Exercise 2 -- Custom Boolean Oracle for a SAT-like Problem\n", - "\n", - "Use `grover.BooleanOracle` to search for all 3-bit numbers where\n", - "**bit 0 AND bit 1 are both 1** (i.e., the two least-significant bits\n", - "are set). The satisfying states are $|011\\rangle = 3$ and\n", - "$|111\\rangle = 7$.\n", - "\n", - "This demonstrates how Grover's algorithm can solve constraint-satisfaction\n", - "problems: you express the constraint as a boolean function and let the\n", - "algorithm find the satisfying assignments." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "c1d2e3f4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the config!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: Boolean oracle for a SAT-like constraint\n", - "// Find all 3-bit numbers x where bit0 AND bit1 == 1\n", - "// (both least-significant bits are set)\n", - "// Satisfying states: |011> = 3 and |111> = 7\n", - "//\n", - "// TODO: Create a BooleanOracle that checks whether both bit0 and bit1 are 1\n", - "// oracle := grover.BooleanOracle(func(x int) bool {\n", - "// bit0 := ???\n", - "// bit1 := ???\n", - "// return ???\n", - "// }, 3)\n", - "//\n", - "// TODO: Run Grover's search with NumSolutions=2 on 3 qubits\n", - "// result, err := grover.Run(ctx, grover.Config{\n", - "// NumQubits: ???,\n", - "// Oracle: oracle,\n", - "// NumSolutions: ???,\n", - "// Shots: 2000,\n", - "// })\n", - "//\n", - "// TODO: Print the top result, iterations used, and measurement counts\n", - "// TODO: Print the success probability for each satisfying state (011, 111)\n", - "// TODO: Visualize with viz.Histogram\n", - "_ = context.Background()\n", - "fmt.Println(\"Uncomment the code above and fill in the config!\")" - ] - }, - { - "cell_type": "markdown", - "id": "d2e3f4a5", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## Key Takeaways\n", - "\n", - "1. **Grover's algorithm** provides a provably optimal quadratic speedup\n", - " for unstructured search: $O(\\sqrt{N})$ queries instead of $O(N)$.\n", - "\n", - "2. The algorithm alternates two operations: an **oracle** that marks target\n", - " states with a phase flip, and a **diffusion operator** that amplifies\n", - " marked amplitudes by reflecting about the mean.\n", - "\n", - "3. **Geometrically**, each iteration rotates the state vector by $2\\theta$\n", - " toward the target subspace. The optimal number of iterations is\n", - " $\\lfloor \\pi/4 \\cdot \\sqrt{N/M} \\rfloor$.\n", - "\n", - "4. **Over-rotation** is a real danger: too many iterations cause the state\n", - " to rotate past the target, reducing the success probability. Unlike\n", - " classical search, more work can make things worse.\n", - "\n", - "5. **Phase oracles** mark targets by index; **boolean oracles** mark\n", - " targets by evaluating a classical predicate. Both are equivalent in\n", - " effect, but boolean oracles are more natural for constraint-satisfaction\n", - " and SAT-like problems.\n", - "\n", - "6. With $M$ multiple solutions, the algorithm needs fewer iterations\n", - " ($\\sqrt{N/M}$ instead of $\\sqrt{N}$) and returns one of the solutions\n", - " uniformly at random.\n", - "\n", - "---\n", - "\n", - "**Next up:** Notebook 10, where we build on these foundations to explore\n", - "more advanced quantum algorithms." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/10-shors-algorithm.ipynb b/notebooks/10-shors-algorithm.ipynb deleted file mode 100644 index a0179da..0000000 --- a/notebooks/10-shors-algorithm.ipynb +++ /dev/null @@ -1,1660 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a0b1c2d3", - "metadata": {}, - "source": [ - "# Notebook 10 -- Shor's Algorithm\n", - "\n", - "**Prerequisites:** Notebooks 01-08. Familiarity with QPE and the QFT.\n", - "\n", - "Shor's algorithm is the most celebrated result in quantum computing: it factors\n", - "integers in polynomial time, breaking the RSA cryptosystem that underpins most\n", - "of today's internet security. Where the best known classical factoring algorithm\n", - "(the general number field sieve) runs in sub-exponential time\n", - "$O(\\exp(n^{1/3} (\\log n)^{2/3}))$, Shor's algorithm runs in $O(n^3)$ using\n", - "quantum phase estimation.\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Describe** how Shor's algorithm reduces factoring to period finding.\n", - "2. **Implement** integer factoring using Goqu's shor package.\n", - "3. **Trace** the period-finding circuit structure and continued fraction extraction.\n", - "\n", - "For quantum counting and amplitude estimation, see [Notebook 10b](10b-quantum-counting.ipynb)." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "b1c2d3e4", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"context\"\n", - "\t\"fmt\"\n", - "\t\"math\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/algorithm/shor\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/sim/statevector\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "c2d3e4f5", - "metadata": {}, - "source": [ - "## The Factoring Problem\n", - "\n", - "Given a composite integer $N$, find two non-trivial factors $p$ and $q$ such\n", - "that $N = p \\times q$. This is believed to be classically hard -- no\n", - "polynomial-time algorithm is known, and RSA encryption relies on this hardness.\n", - "\n", - "For example, given $N = 15$, the factors are $3$ and $5$. For small numbers\n", - "this is trivial, but for numbers with hundreds of digits, classical algorithms\n", - "become infeasible." - ] - }, - { - "cell_type": "markdown", - "id": "d3e4f5a6", - "metadata": {}, - "source": [ - "## How Shor's Algorithm Works\n", - "\n", - "Shor's algorithm converts factoring into **period finding** through the\n", - "following steps:\n", - "\n", - "1. **Choose a random base** $a$ coprime to $N$.\n", - "2. **Find the period** $r$ of $f(x) = a^x \\bmod N$ -- the smallest $r$ such\n", - " that $a^r \\equiv 1 \\pmod{N}$.\n", - "3. **Extract factors**: if $r$ is even, compute\n", - " $\\gcd(a^{r/2} \\pm 1, N)$ which, with high probability, yields a\n", - " non-trivial factor.\n", - "\n", - "The quantum speedup lives in step 2. Classically, finding the period requires\n", - "exponentially many evaluations of $f$. Quantum mechanically, we use **Quantum\n", - "Phase Estimation (QPE)** on the modular exponentiation unitary\n", - "$U|y\\rangle = |ay \\bmod N\\rangle$.\n", - "\n", - "The eigenvalues of $U$ are $e^{2\\pi i s/r}$ for $s = 0, \\ldots, r-1$, so QPE\n", - "measures a phase $\\varphi \\approx s/r$. A **continued fraction expansion** of\n", - "$\\varphi$ recovers the period $r$, from which we compute the factors.\n", - "\n", - "### Circuit Structure\n", - "\n", - "The quantum circuit consists of:\n", - "\n", - "1. A **phase register** of $t$ qubits initialized in superposition.\n", - "2. A **target register** of $\\lceil\\log_2 N\\rceil$ qubits initialized to $|1\\rangle$.\n", - "3. **Controlled modular exponentiation**: $\\text{C-}U^{2^k}$ for each phase qubit $k$.\n", - "4. **Inverse QFT** on the phase register to extract the phase.\n", - "5. **Measurement** of the phase register, followed by classical post-processing." - ] - }, - { - "cell_type": "markdown", - "id": "uuyl8oeabko", - "metadata": {}, - "source": [ - "### The period-finding circuit structure\n", - "\n", - "Shor's algorithm uses QPE to find the period of modular exponentiation.\n", - "While the full circuit for large numbers is complex, we can demonstrate\n", - "the structure: a phase register in superposition, controlled modular\n", - "exponentiation, and inverse QFT.\n", - "\n", - "For N=15 with base a=2, the modular exponentiation is U|x> = |2x mod 15>.\n", - "Let's trace through the key idea: QPE extracts the period from the phase." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "d7uebigc89e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Shor's Algorithm for N=15\n", - "=========================\n", - "\n", - "Step 1: Choose random base a=2\n", - "Step 2: Compute 2^k mod 15 for k=1,2,...,8:\n", - " 2^1 mod 15 = 2\n", - " 2^2 mod 15 = 4\n", - " 2^3 mod 15 = 8\n", - " 2^4 mod 15 = 1 <-- period found! r=4\n", - " 2^5 mod 15 = 2\n", - " 2^6 mod 15 = 4\n", - " 2^7 mod 15 = 8\n", - " 2^8 mod 15 = 1 <-- period found! r=8\n", - "\n", - "Step 3: Period r=4. Factors from gcd(2^(r/2) ± 1, 15):\n", - " gcd(2^2 + 1, 15) = gcd(5, 15) = 5\n", - " gcd(2^2 - 1, 15) = gcd(3, 15) = 3\n", - " Therefore 15 = 3 × 5\n" - ] - } - ], - "source": [ - "%%\n", - "// Shor's algorithm structure: QPE on modular exponentiation\n", - "// For N=15, a=2: the period is r=4 (2^4 = 16 ≡ 1 mod 15)\n", - "//\n", - "// The full circuit has:\n", - "// - Phase register (t qubits in superposition)\n", - "// - Work register (holding the modular exponentiation result)\n", - "// - Controlled-U^(2^k) operations\n", - "// - Inverse QFT on the phase register\n", - "//\n", - "// We can verify the math: if r=4 and we use 3 phase bits,\n", - "// QPE should output phases that are multiples of 1/r = 1/4\n", - "\n", - "fmt.Println(\"Shor's Algorithm for N=15\")\n", - "fmt.Println(\"=========================\")\n", - "fmt.Println()\n", - "fmt.Println(\"Step 1: Choose random base a=2\")\n", - "fmt.Println(\"Step 2: Compute 2^k mod 15 for k=1,2,...,8:\")\n", - "for k := 1; k <= 8; k++ {\n", - "\tval := 1\n", - "\tfor i := 0; i < k; i++ {\n", - "\t\tval = (val * 2) % 15\n", - "\t}\n", - "\tmarker := \"\"\n", - "\tif val == 1 {\n", - "\t\tmarker = \" <-- period found! r=\" + fmt.Sprint(k)\n", - "\t}\n", - "\tfmt.Printf(\" 2^%d mod 15 = %d%s\\n\", k, val, marker)\n", - "}\n", - "fmt.Println()\n", - "fmt.Println(\"Step 3: Period r=4. Factors from gcd(2^(r/2) ± 1, 15):\")\n", - "fmt.Printf(\" gcd(2^2 + 1, 15) = gcd(5, 15) = 5\\n\")\n", - "fmt.Printf(\" gcd(2^2 - 1, 15) = gcd(3, 15) = 3\\n\")\n", - "fmt.Printf(\" Therefore 15 = 3 × 5\\n\")" - ] - }, - { - "cell_type": "markdown", - "id": "6ebjtlfpa9w", - "metadata": {}, - "source": [ - "Now let's run the full algorithm using Goqu's `shor` package:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "e4f5a6b7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "N = 15 = 3 x 5\n", - "Period: 0, Base: 12, Attempts: 2\n", - "\n", - "Verification: 3 x 5 = 15\n" - ] - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Factor N = 15 using Shor's algorithm\n", - "result, err := shor.Run(ctx, shor.Config{\n", - "\tN: 15,\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"N = 15 = %d x %d\\n\", result.Factors[0], result.Factors[1])\n", - "fmt.Printf(\"Period: %d, Base: %d, Attempts: %d\\n\", result.Period, result.Base, result.Attempts)\n", - "fmt.Printf(\"\\nVerification: %d x %d = %d\\n\",\n", - "\tresult.Factors[0], result.Factors[1],\n", - "\tresult.Factors[0]*result.Factors[1])" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "f5a6b7c8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Order-finding circuit (QPE + modular exponentiation):\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "q3\n", - "\n", - "q4\n", - "\n", - "q5\n", - "\n", - "q6\n", - "\n", - "q7\n", - "\n", - "q8\n", - "\n", - "q9\n", - "\n", - "q10\n", - "\n", - "q11\n", - "\n", - "X\n", - "H\n", - "H\n", - "H\n", - "H\n", - "H\n", - "H\n", - "H\n", - "H\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "C3-X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "H\n", - "\n", - "\n", - "P(-pi/2)\n", - "H\n", - "\n", - "\n", - "P(-pi/4)\n", - "\n", - "\n", - "P(-pi/2)\n", - "H\n", - "\n", - "\n", - "P(-pi/8)\n", - "\n", - "\n", - "P(-pi/4)\n", - "\n", - "\n", - "P(-pi/2)\n", - "H\n", - "\n", - "\n", - "P(-pi/16)\n", - "\n", - "\n", - "P(-pi/8)\n", - "\n", - "\n", - "P(-pi/4)\n", - "\n", - "\n", - "P(-pi/2)\n", - "H\n", - "\n", - "\n", - "P(-0.098..\n", - "\n", - "\n", - "P(-pi/16)\n", - "\n", - "\n", - "P(-pi/8)\n", - "\n", - "\n", - "P(-pi/4)\n", - "\n", - "\n", - "P(-pi/2)\n", - "H\n", - "\n", - "\n", - "P(-0.049..\n", - "\n", - "\n", - "P(-0.098..\n", - "\n", - "\n", - "P(-pi/16)\n", - "\n", - "\n", - "P(-pi/8)\n", - "\n", - "\n", - "P(-pi/4)\n", - "\n", - "\n", - "P(-pi/2)\n", - "H\n", - "\n", - "\n", - "P(-0.024..\n", - "\n", - "\n", - "P(-0.049..\n", - "\n", - "\n", - "P(-0.098..\n", - "\n", - "\n", - "P(-pi/16)\n", - "\n", - "\n", - "P(-pi/8)\n", - "\n", - "\n", - "P(-pi/4)\n", - "\n", - "\n", - "P(-pi/2)\n", - "H\n", - "M\n", - "M\n", - "M\n", - "M\n", - "M\n", - "M\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Run Shor's on N=15 again and display the order-finding circuit\n", - "result, err := shor.Run(ctx, shor.Config{\n", - "\tN: 15,\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "if result.Circuit != nil {\n", - "\tfmt.Println(\"Order-finding circuit (QPE + modular exponentiation):\")\n", - "\tgonbui.DisplayHTML(draw.SVG(result.Circuit))\n", - "} else {\n", - "\tfmt.Println(\"Factor found classically (no circuit needed for this attempt).\")\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "a6b7c8d9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "QPE measurement histogram for N=15:\n", - "Peaks at multiples of 1/r reveal the period r.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "Shor's Algorithm: QPE Output (N=15)\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "50\n", - "\n", - "100\n", - "\n", - "150\n", - "\n", - "200\n", - "\n", - "250\n", - "\n", - "000100000000\n", - "\n", - "000100000010\n", - "\n", - "000100000011\n", - "\n", - "001000000000\n", - "\n", - "001000000001\n", - "\n", - "001000000010\n", - "\n", - "001000000011\n", - "\n", - "111000000000\n", - "\n", - "111000000001\n", - "\n", - "111000000010\n", - "\n", - "111000000011\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Run Shor's on N=15 and display the QPE measurement histogram.\n", - "// The circuit measures only the phase register; peaks correspond to\n", - "// phases s/r that encode the period r.\n", - "result, err := shor.Run(ctx, shor.Config{\n", - "\tN: 15,\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "if result.Circuit != nil {\n", - "\t// Re-run the circuit to get measurement counts for visualization\n", - "\tnTarget := int(math.Ceil(math.Log2(float64(15))))\n", - "\tnPhase := 2 * nTarget\n", - "\tnTotal := nPhase + nTarget\n", - "\tsim := statevector.New(nTotal)\n", - "\tcounts, err := sim.Run(result.Circuit, 1000)\n", - "\tif err != nil {\n", - "\t\tpanic(err)\n", - "\t}\n", - "\tfmt.Println(\"QPE measurement histogram for N=15:\")\n", - "\tfmt.Println(\"Peaks at multiples of 1/r reveal the period r.\")\n", - "\tsvg := viz.Histogram(counts, viz.WithTitle(\"Shor's Algorithm: QPE Output (N=15)\"))\n", - "\tgonbui.DisplayHTML(svg)\n", - "} else {\n", - "\tfmt.Println(\"Factor found classically -- no quantum circuit to visualize.\")\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "f1a2b3c4", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** What are the factors of $N = 21$? What period $r$ might Shor's algorithm discover?\n", - "\n", - "**Pause and predict** before running the next cell.\n", - "\n", - "*Hint: Pick a base $a$ coprime to 21 and compute $a^x \\bmod 21$ for $x = 1, 2, 3, \\ldots$ until you find the period. Then use $\\gcd(a^{r/2} \\pm 1, N)$ to extract the factors.*" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "a2b3c4d5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "N = 21 = 7 x 3\n", - "Period: 2, Base: 8, Attempts: 1\n", - "\n", - "Verification: 7 x 3 = 21\n", - "\n", - "Prediction confirmed: 21 = 3 x 7.\n" - ] - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Factor N = 21\n", - "result, err := shor.Run(ctx, shor.Config{\n", - "\tN: 21,\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"N = 21 = %d x %d\\n\", result.Factors[0], result.Factors[1])\n", - "fmt.Printf(\"Period: %d, Base: %d, Attempts: %d\\n\", result.Period, result.Base, result.Attempts)\n", - "fmt.Printf(\"\\nVerification: %d x %d = %d\\n\",\n", - "\tresult.Factors[0], result.Factors[1],\n", - "\tresult.Factors[0]*result.Factors[1])\n", - "fmt.Println(\"\\nPrediction confirmed: 21 = 3 x 7.\")" - ] - }, - { - "cell_type": "markdown", - "id": "j3890whx4pa", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. Why does Shor's algorithm need a quantum computer for period finding but not for the GCD step?\n", - "2. What is the relationship between the period r and the factors of N?\n", - "3. Why does Shor's algorithm fail when r is odd, and how likely is this?" - ] - }, - { - "cell_type": "markdown", - "id": "b3c4d5e6", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## Exercises\n", - "\n", - "### Exercise 1 -- Factor N = 35\n", - "\n", - "Run Shor's algorithm on $N = 35$. Verify that the returned factors are correct\n", - "and print the period and base that the algorithm discovered." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "c4d5e6f7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the config!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: Factor N = 35\n", - "// Expected factors: 5 and 7\n", - "//\n", - "// TODO: Run Shor's algorithm on N = 35\n", - "// result, err := shor.Run(ctx, shor.Config{\n", - "// N: ???,\n", - "// Shots: 1000,\n", - "// })\n", - "//\n", - "// TODO: Print the factors, period, base, and number of attempts\n", - "// fmt.Printf(\"N = 35 = %d x %d\\n\", result.Factors[0], result.Factors[1])\n", - "//\n", - "// TODO: Verify correctness by checking result.Factors[0]*result.Factors[1] == 35\n", - "_ = context.Background()\n", - "fmt.Println(\"Uncomment the code above and fill in the config!\")" - ] - }, - { - "cell_type": "markdown", - "id": "pwzxwsrs9im", - "metadata": {}, - "source": [ - "## Post-Quantum Cryptography\n", - "\n", - "Shor's algorithm poses a direct threat to RSA, Diffie-Hellman, and\n", - "elliptic curve cryptography -- the foundations of internet security.\n", - "The world is already responding:\n", - "\n", - "- In 2024, **NIST finalized three post-quantum cryptography standards**:\n", - " CRYSTALS-Kyber (key encapsulation), CRYSTALS-Dilithium (digital\n", - " signatures), and SPHINCS+ (hash-based signatures). These algorithms\n", - " are believed to be secure against both classical and quantum computers.\n", - "\n", - "- Major tech companies (Google, Apple, Signal) have begun deploying\n", - " post-quantum cryptography in production systems, even though\n", - " fault-tolerant quantum computers capable of breaking RSA do not\n", - " yet exist. This proactive migration guards against \"harvest now,\n", - " decrypt later\" attacks.\n", - "\n", - "- Current quantum hardware is far from being able to factor\n", - " cryptographically relevant numbers ($> 2000$ bits). The largest number\n", - " factored by a quantum computer is still very small. But the migration\n", - " to post-quantum standards is happening now because cryptographic\n", - " transitions take decades.\n", - "\n", - "Shor's algorithm remains the most compelling demonstration of quantum\n", - "computational advantage -- it motivated both the field of quantum\n", - "computing and the field of post-quantum cryptography." - ] - }, - { - "cell_type": "markdown", - "id": "f7a8b9c0", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "\n", - "1. **Shor's algorithm** factors integers in polynomial time by reducing\n", - " factoring to period finding. The quantum speedup comes from QPE on the\n", - " modular exponentiation unitary, followed by continued fraction expansion\n", - " to extract the period.\n", - "\n", - "2. **The order-finding circuit** uses controlled modular exponentiation\n", - " $\\text{C-}U^{2^k}$ followed by the inverse QFT. The phase register\n", - " encodes $s/r$ where $r$ is the period, and the factors are recovered via\n", - " $\\gcd(a^{r/2} \\pm 1, N)$.\n", - "\n", - "3. The algorithm is **probabilistic**: a random base $a$ might yield an odd\n", - " period or trivial factors, but repeating with different bases succeeds\n", - " with high probability after a few attempts.\n", - "\n", - "4. While Shor's algorithm is polynomial in theory, the circuits for\n", - " modular exponentiation of large numbers are very deep, making practical\n", - " factoring of cryptographically relevant numbers a goal for future\n", - " fault-tolerant quantum computers.\n", - "\n", - "For quantum counting and amplitude estimation, see [Notebook 10b](10b-quantum-counting.ipynb).\n", - "\n", - "---\n", - "\n", - "**Next up:** Notebook 11, where we will explore quantum error correction\n", - "and how to protect quantum information from noise." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/10b-quantum-counting.ipynb b/notebooks/10b-quantum-counting.ipynb deleted file mode 100644 index e82c547..0000000 --- a/notebooks/10b-quantum-counting.ipynb +++ /dev/null @@ -1,698 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "qc-intro-01", - "metadata": {}, - "source": [ - "# Notebook 10b -- Quantum Counting and Amplitude Estimation\n", - "\n", - "**Prerequisites:** Notebooks 08-09. Familiarity with QPE and Grover's algorithm.\n", - "\n", - "In the previous notebook we saw how Shor's algorithm uses Quantum Phase\n", - "Estimation (QPE) to find the period of modular exponentiation, enabling\n", - "efficient integer factoring. In this notebook we explore two more powerful\n", - "applications of QPE: **quantum counting** and **amplitude estimation**.\n", - "\n", - "Quantum counting tells us *how many* solutions a search problem has, while\n", - "amplitude estimation generalizes this to estimate the probability amplitude\n", - "of any \"good\" subspace. Both provide quadratic speedups over their classical\n", - "counterparts and serve as core subroutines in quantum algorithms for Monte\n", - "Carlo simulation, optimization, and machine learning.\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Explain** how quantum counting uses QPE on the Grover iterate to\n", - " estimate the number of solutions to a search problem.\n", - "2. **Run** quantum counting on a small oracle and interpret the results.\n", - "3. **Describe** how amplitude estimation generalizes quantum counting.\n", - "4. **Compare** standard and iterative amplitude estimation in terms of\n", - " accuracy and resource usage." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "qc-imports-02", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"context\"\n", - "\t\"fmt\"\n", - "\t\"math\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/algorithm/ampest\"\n", - "\t\"github.com/splch/goqu/algorithm/counting\"\n", - "\t\"github.com/splch/goqu/algorithm/grover\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "qc-counting-md-03", - "metadata": {}, - "source": [ - "## Quantum Counting\n", - "\n", - "**Quantum counting** answers the question: *how many solutions does a search\n", - "problem have?* This is useful when we need to know the number of marked states\n", - "$M$ in a search space of size $N = 2^n$ before running Grover's search (since\n", - "the optimal iteration count depends on $M$).\n", - "\n", - "The algorithm works by applying **QPE to the Grover iterate** $Q$. The Grover\n", - "operator has eigenvalues $e^{\\pm 2i\\theta}$ where $\\sin^2(\\theta) = M/N$.\n", - "QPE estimates $\\theta$, from which we recover:\n", - "\n", - "$$M = N \\sin^2(\\theta)$$\n", - "\n", - "With $t$ phase bits, the estimate achieves $t$ bits of precision. This gives a\n", - "quadratic speedup over classical counting, which requires $O(N)$ oracle queries." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "qc-counting-code-04", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Estimated count: 2.0 (expected: 2)\n", - "Phase: 0.7500\n", - "Measurement counts: map[000010:123 000011:125 010010:124 010011:136 100010:111 100011:122 110010:131 110011:128]\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "Quantum Counting: 2 Solutions in 4 States\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "20\n", - "\n", - "40\n", - "\n", - "60\n", - "\n", - "80\n", - "\n", - "100\n", - "\n", - "120\n", - "\n", - "140\n", - "\n", - "000010\n", - "\n", - "000011\n", - "\n", - "010010\n", - "\n", - "010011\n", - "\n", - "100010\n", - "\n", - "100011\n", - "\n", - "110010\n", - "\n", - "110011\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Use quantum counting to estimate the number of solutions\n", - "// to a Grover oracle. We mark states |01> and |10> (2 solutions\n", - "// out of 4 total states in a 2-qubit search space).\n", - "oracle := grover.PhaseOracle([]int{0b01, 0b10}, 2)\n", - "\n", - "result, err := counting.Run(ctx, counting.Config{\n", - "\tNumQubits: 2,\n", - "\tOracle: oracle,\n", - "\tNumPhaseBits: 4,\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"Estimated count: %.1f (expected: 2)\\n\", result.Count)\n", - "fmt.Printf(\"Phase: %.4f\\n\", result.Phase)\n", - "fmt.Println(\"Measurement counts:\", result.Counts)\n", - "\n", - "svg := viz.Histogram(result.Counts, viz.WithTitle(\"Quantum Counting: 2 Solutions in 4 States\"))\n", - "gonbui.DisplayHTML(svg)" - ] - }, - { - "cell_type": "markdown", - "id": "qc-counting-interp-05", - "metadata": {}, - "source": [ - "The histogram shows peaks at phase register values whose binary fractions\n", - "approximate $\\theta / \\pi$. From the dominant peak we extract $\\theta$ and\n", - "compute $M = N \\sin^2(\\theta) \\approx 2$, confirming that our 2-qubit search\n", - "space has exactly 2 marked states." - ] - }, - { - "cell_type": "markdown", - "id": "qc-ampest-md-06", - "metadata": {}, - "source": [ - "## Amplitude Estimation\n", - "\n", - "**Amplitude estimation** generalizes quantum counting. Instead of counting\n", - "the number of solutions, it estimates the **probability amplitude** $a$ of a\n", - "\"good\" subspace prepared by a state-preparation circuit $A$.\n", - "\n", - "Given:\n", - "- A state-preparation circuit $A$ that creates $A|0\\rangle = \\sqrt{1-a^2}|\\psi_0\\rangle + a|\\psi_1\\rangle$\n", - "- An oracle $S_f$ that marks the \"good\" states $|\\psi_1\\rangle$\n", - "\n", - "Standard amplitude estimation builds the **Grover iterate**\n", - "$Q = A \\cdot S_0 \\cdot A^\\dagger \\cdot S_f$ and applies QPE to estimate the\n", - "phase $\\theta$ where $a = \\sin(\\pi\\theta)$.\n", - "\n", - "This is a core subroutine in:\n", - "- **Monte Carlo methods** (quadratic speedup for financial derivative pricing)\n", - "- **Quantum machine learning** (estimating inner products)\n", - "- **Optimization** (evaluating objective functions)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "qc-ampest-code-07", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Amplitude: 0.5556 (expected: 0.5000)\n", - "Probability: 0.3087 (expected: 0.2500)\n", - "Phase: 0.6875\n", - "Measurement counts: map[000000:1 000001:3 000010:9 000011:3 000100:1 000101:8 000110:11 000111:1 001000:2 001001:2 001010:60 001011:1 001100:1 001101:64 001110:2 010000:1 010001:1 010010:4 010011:7 010100:1 010101:10 010110:12 010111:4 011001:1 011010:63 011011:1 011100:3 011101:66 011110:2 100001:1 100010:5 100011:3 100100:5 100101:9 100110:9 101001:1 101010:69 101011:2 101101:60 101110:1 110000:2 110001:9 110010:3 110011:11 110100:2 110101:45 110110:63 110111:2 111000:1 111001:6 111010:152 111011:2 111100:2 111101:175 111110:14 111111:1]\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "Amplitude Estimation: P(|11>) in Uniform State\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "50\n", - "\n", - "100\n", - "\n", - "150\n", - "\n", - "200\n", - "\n", - "000000\n", - "\n", - "000001\n", - "\n", - "000010\n", - "\n", - "000011\n", - "\n", - "000100\n", - "\n", - "000101\n", - "\n", - "000110\n", - "\n", - "000111\n", - "\n", - "001000\n", - "\n", - "001001\n", - "\n", - "001010\n", - "\n", - "001011\n", - "\n", - "001100\n", - "\n", - "001101\n", - "\n", - "001110\n", - "\n", - "010000\n", - "\n", - "010001\n", - "\n", - "010010\n", - "\n", - "010011\n", - "\n", - "010100\n", - "\n", - "010101\n", - "\n", - "010110\n", - "\n", - "010111\n", - "\n", - "011001\n", - "\n", - "011010\n", - "\n", - "011011\n", - "\n", - "011100\n", - "\n", - "011101\n", - "\n", - "011110\n", - "\n", - "100001\n", - "\n", - "100010\n", - "\n", - "100011\n", - "\n", - "100100\n", - "\n", - "100101\n", - "\n", - "100110\n", - "\n", - "101001\n", - "\n", - "101010\n", - "\n", - "101011\n", - "\n", - "101101\n", - "\n", - "101110\n", - "\n", - "110000\n", - "\n", - "110001\n", - "\n", - "110010\n", - "\n", - "110011\n", - "\n", - "110100\n", - "\n", - "110101\n", - "\n", - "110110\n", - "\n", - "110111\n", - "\n", - "111000\n", - "\n", - "111001\n", - "\n", - "111010\n", - "\n", - "111011\n", - "\n", - "111100\n", - "\n", - "111101\n", - "\n", - "111110\n", - "\n", - "111111\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Amplitude estimation: estimate the amplitude of |11> in the\n", - "// uniform superposition H|0>H|0> = (|00>+|01>+|10>+|11>)/2.\n", - "// The amplitude of |11> is 1/2, so probability = 1/4.\n", - "statePrep, err := builder.New(\"prep\", 2).H(0).H(1).Build()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "oracle := grover.PhaseOracle([]int{0b11}, 2)\n", - "\n", - "result, err := ampest.Run(ctx, ampest.Config{\n", - "\tStatePrep: statePrep,\n", - "\tOracle: oracle,\n", - "\tNumQubits: 2,\n", - "\tNumPhaseBits: 4,\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"Amplitude: %.4f (expected: 0.5000)\\n\", result.Amplitude)\n", - "fmt.Printf(\"Probability: %.4f (expected: 0.2500)\\n\", result.Probability)\n", - "fmt.Printf(\"Phase: %.4f\\n\", result.Phase)\n", - "fmt.Println(\"Measurement counts:\", result.Counts)\n", - "\n", - "svg := viz.Histogram(result.Counts, viz.WithTitle(\"Amplitude Estimation: P(|11>) in Uniform State\"))\n", - "gonbui.DisplayHTML(svg)" - ] - }, - { - "cell_type": "markdown", - "id": "qc-predict-md-08", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** A 3-qubit search space has $N = 8$ states. If we mark states\n", - "$|001\\rangle$, $|011\\rangle$, and $|101\\rangle$ (3 solutions), what count\n", - "should quantum counting return?\n", - "\n", - "**Prediction:** The search space has $N = 8$ states with $M = 3$ solutions.\n", - "Quantum counting estimates $M$ via $M = N \\sin^2(\\theta)$. With enough phase\n", - "bits the estimate should be close to $3$.\n", - "\n", - "Let's verify." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "qc-predict-code-09", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Estimated count: 2.84 (expected: 3)\n", - "Phase: 0.7031\n", - "\n", - "Prediction confirmed: quantum counting correctly estimates M ≈ 3.\n" - ] - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// 3-qubit search space: mark |001>=1, |011>=3, |101>=5\n", - "oracle := grover.PhaseOracle([]int{0b001, 0b011, 0b101}, 3)\n", - "\n", - "result, err := counting.Run(ctx, counting.Config{\n", - "\tNumQubits: 3,\n", - "\tOracle: oracle,\n", - "\tNumPhaseBits: 6,\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"Estimated count: %.2f (expected: 3)\\n\", result.Count)\n", - "fmt.Printf(\"Phase: %.4f\\n\", result.Phase)\n", - "fmt.Printf(\"\\nPrediction confirmed: quantum counting correctly estimates M ≈ 3.\\n\")" - ] - }, - { - "cell_type": "markdown", - "id": "v9d5k5aok47", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. Why does quantum counting apply QPE to the Grover iterate rather than to the oracle directly?\n", - "2. How does the number of phase bits affect the precision of the count estimate?\n", - "3. What is the main resource advantage of iterative amplitude estimation over standard amplitude estimation?" - ] - }, - { - "cell_type": "markdown", - "id": "qc-exercises-hdr-10", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## Exercises" - ] - }, - { - "cell_type": "markdown", - "id": "qc-ex1-md-11", - "metadata": {}, - "source": [ - "### Exercise 1 -- Quantum Counting with a Single Solution\n", - "\n", - "Use quantum counting to estimate the number of solutions when only one state\n", - "is marked in a 3-qubit search space ($N = 8$, $M = 1$). How close is the\n", - "estimate to $1$? Try increasing `NumPhaseBits` to see how precision improves." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "qc-ex1-code-12", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the config!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: Quantum counting with a single marked state\n", - "// Search space: 3 qubits (N=8), mark only |101> (M=1)\n", - "//\n", - "// TODO: Create a phase oracle marking a single state\n", - "// oracle := grover.PhaseOracle([]int{???}, 3)\n", - "//\n", - "// TODO: Run quantum counting with 4 phase bits\n", - "// result, err := counting.Run(ctx, counting.Config{\n", - "// NumQubits: ???,\n", - "// Oracle: oracle,\n", - "// NumPhaseBits: ???,\n", - "// Shots: 1000,\n", - "// })\n", - "//\n", - "// TODO: Print the estimated count and compare to expected M=1\n", - "//\n", - "// TODO: Try again with NumPhaseBits=6 and NumPhaseBits=8.\n", - "// How does precision change?\n", - "_ = context.Background()\n", - "fmt.Println(\"Uncomment the code above and fill in the config!\")" - ] - }, - { - "cell_type": "markdown", - "id": "qc-ex2-md-13", - "metadata": {}, - "source": [ - "### Exercise 2 -- Compare Standard vs Iterative Amplitude Estimation\n", - "\n", - "Standard amplitude estimation uses QPE with a full ancilla register, building\n", - "an $O(2^t)$-depth circuit. **Iterative amplitude estimation** avoids the large\n", - "ancilla register by using a single ancilla qubit and progressively doubling the\n", - "Grover power, achieving similar precision with lower circuit depth.\n", - "\n", - "Run both methods on the same problem (estimating the amplitude of $|11\\rangle$\n", - "in the uniform 2-qubit superposition) and compare their accuracy." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "qc-ex2-code-14", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the config!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: Standard vs Iterative Amplitude Estimation\n", - "// Estimate the amplitude of |11> in the uniform 2-qubit superposition.\n", - "// The amplitude of |11> is 1/2, so probability = 1/4.\n", - "//\n", - "// TODO: Build the state preparation circuit (H on both qubits)\n", - "// statePrep, err := builder.New(\"prep\", 2).H(0).H(1).Build()\n", - "//\n", - "// TODO: Create a phase oracle marking |11>\n", - "// oracle := grover.PhaseOracle([]int{???}, 2)\n", - "//\n", - "// TODO: Run standard amplitude estimation with 4 phase bits\n", - "// stdResult, err := ampest.Run(ctx, ampest.Config{\n", - "// StatePrep: statePrep,\n", - "// Oracle: oracle,\n", - "// NumQubits: ???,\n", - "// NumPhaseBits: ???,\n", - "// Shots: 1000,\n", - "// })\n", - "//\n", - "// TODO: Run iterative amplitude estimation with 4 max iterations\n", - "// iterResult, err := ampest.RunIterative(ctx, ampest.IterativeConfig{\n", - "// StatePrep: statePrep,\n", - "// Oracle: oracle,\n", - "// NumQubits: ???,\n", - "// MaxIters: ???,\n", - "// Shots: 1000,\n", - "// })\n", - "//\n", - "// TODO: Print and compare both results:\n", - "// - Amplitude and error vs expected (0.5)\n", - "// - Probability\n", - "// - For iterative: confidence interval and iteration count\n", - "// - Note the resource tradeoff: standard AE uses ancilla qubits, iterative does not\n", - "_ = context.Background()\n", - "fmt.Println(\"Uncomment the code above and fill in the config!\")" - ] - }, - { - "cell_type": "markdown", - "id": "qc-ex3-md-15", - "metadata": {}, - "source": [ - "### Exercise 3 -- Amplitude Estimation for a Biased State\n", - "\n", - "Instead of the uniform superposition, prepare a **biased** state where qubit 0\n", - "has a rotation $R_y(\\pi/3)$ and qubit 1 has $R_y(\\pi/6)$. Mark the $|11\\rangle$\n", - "state and use amplitude estimation to determine its probability.\n", - "\n", - "*Hint:* The expected amplitude is $\\sin(\\pi/6) \\cdot \\sin(\\pi/12) \\approx 0.129$\n", - "and the expected probability is $\\approx 0.0167$." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "qc-ex3-code-16", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the config!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 3: Amplitude estimation with a biased state\n", - "//\n", - "// TODO: Build a state-prep circuit with Ry(pi/3) on qubit 0 and Ry(pi/6) on qubit 1\n", - "// statePrep, err := builder.New(\"biased\", 2).Ry(0, math.Pi/3).Ry(1, math.Pi/6).Build()\n", - "//\n", - "// TODO: Create a phase oracle marking |11>\n", - "// oracle := grover.PhaseOracle([]int{???}, 2)\n", - "//\n", - "// TODO: Run amplitude estimation with 6 phase bits for higher precision\n", - "// result, err := ampest.Run(ctx, ampest.Config{\n", - "// StatePrep: statePrep,\n", - "// Oracle: oracle,\n", - "// NumQubits: ???,\n", - "// NumPhaseBits: ???,\n", - "// Shots: 1000,\n", - "// })\n", - "//\n", - "// TODO: Print estimated amplitude and probability, compare to expected values\n", - "// Expected amplitude ≈ 0.129, expected probability ≈ 0.0167\n", - "_ = context.Background()\n", - "fmt.Println(\"Uncomment the code above and fill in the config!\")" - ] - }, - { - "cell_type": "markdown", - "id": "qc-takeaways-17", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "\n", - "1. **Quantum counting** uses QPE on the Grover iterate to estimate the\n", - " number of solutions $M$ in a search space of size $N$. The Grover\n", - " eigenvalue $e^{\\pm 2i\\theta}$ encodes $M/N = \\sin^2(\\theta)$.\n", - "\n", - "2. **Amplitude estimation** generalizes quantum counting by estimating the\n", - " probability amplitude of a \"good\" subspace prepared by an arbitrary\n", - " state-preparation circuit. It is a core primitive for quantum speedups\n", - " in Monte Carlo methods, optimization, and machine learning.\n", - "\n", - "3. **Iterative amplitude estimation** achieves similar precision to standard\n", - " AE without the large ancilla register, trading circuit width for depth\n", - " through progressive Grover power doubling.\n", - "\n", - "4. **Precision scales with phase bits**: more phase register qubits (or\n", - " more iterations in the iterative variant) yield higher precision\n", - " estimates at the cost of deeper circuits.\n", - "\n", - "5. Both techniques provide a **quadratic speedup** over classical sampling,\n", - " requiring $O(1/\\epsilon)$ oracle calls to achieve precision $\\epsilon$\n", - " compared to the classical $O(1/\\epsilon^2)$.\n", - "\n", - "---\n", - "\n", - "**Next up:** Notebook 11, where we will explore quantum error correction\n", - "and how to protect quantum information from noise." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/11-noise-and-decoherence.ipynb b/notebooks/11-noise-and-decoherence.ipynb deleted file mode 100644 index eab89d6..0000000 --- a/notebooks/11-noise-and-decoherence.ipynb +++ /dev/null @@ -1,1226 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "cell-0", - "metadata": {}, - "source": [ - "# 11 - Noise, Decoherence, and Density Matrices\n", - "\n", - "**Prerequisites:** Notebooks 01-05. Familiarity with entanglement and statevector simulation.\n", - "\n", - "Real quantum hardware does not evolve unitarily forever. Every qubit is an\n", - "**open quantum system** -- it interacts with its environment, and those\n", - "interactions introduce errors. Understanding noise is essential for building\n", - "practical quantum algorithms, because a circuit that works perfectly in\n", - "simulation may produce garbage on a real device.\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Describe** the density matrix formalism and distinguish pure from mixed states.\n", - "2. **Implement** noisy simulations using different noise channels.\n", - "3. **Analyze** how noise strength affects fidelity and purity.\n", - "4. **Explain** the physical origins of T1 and T2 decoherence.\n", - "\n", - "In this notebook we will:\n", - "\n", - "1. Move from statevectors to **density matrices**, the natural language for\n", - " mixed (noisy) quantum states.\n", - "2. Explore **noise channels** -- the mathematical description of how errors\n", - " affect qubits -- via Kraus operators.\n", - "3. Quantify noise with **purity** and **fidelity**.\n", - "4. Survey the standard channels: depolarizing, amplitude damping, phase\n", - " damping, bit flip, and phase flip.\n", - "5. Model realistic decoherence with **T1/T2 thermal relaxation**.\n", - "6. Build **custom noise channels** from scratch.\n", - "\n", - "### Misconception: Quantum error correction is just classical error correction\n", - "\n", - "Classical error correction works by copying bits. Quantum error correction\n", - "cannot do this -- the **no-cloning theorem** forbids copying an unknown\n", - "quantum state. Instead, quantum EC encodes logical qubits into entangled\n", - "states of many physical qubits, using syndrome measurements that extract\n", - "error information without disturbing the encoded data. This is a\n", - "fundamentally different (and harder) problem." - ] - }, - { - "cell_type": "markdown", - "id": "0xpia4i1q5bf", - "metadata": {}, - "source": [ - "### Why this matters\n", - "\n", - "Every algorithm you have built so far -- from single-qubit gates to Shor's factoring -- works perfectly in simulation. On real hardware, **none of them would**. Gate errors, decoherence, and measurement noise corrupt quantum states in ways that have no classical analogue. Understanding noise is essential for designing circuits that produce useful results on today's quantum processors, and for knowing when a quantum computation can be trusted." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "cell-1", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"fmt\"\n", - "\t\"math\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/sim/densitymatrix\"\n", - "\t\"github.com/splch/goqu/sim/noise\"\n", - "\t\"github.com/splch/goqu/sim/statevector\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-2", - "metadata": {}, - "source": [ - "## From Statevectors to Density Matrices\n", - "\n", - "So far we have represented quantum states as **statevectors** $|\\psi\\rangle$.\n", - "This works perfectly for isolated systems undergoing unitary evolution. But\n", - "when a qubit interacts with its environment, the system's state can no longer\n", - "be described by a single statevector -- it becomes a statistical mixture of\n", - "pure states.\n", - "\n", - "The **density matrix** $\\rho$ handles both cases:\n", - "\n", - "- **Pure state:** $\\rho = |\\psi\\rangle\\langle\\psi|$, with $\\text{Tr}(\\rho^2) = 1$\n", - "- **Mixed state:** $\\rho = \\sum_i p_i |\\psi_i\\rangle\\langle\\psi_i|$, with $\\text{Tr}(\\rho^2) < 1$\n", - "\n", - "The quantity $\\text{Tr}(\\rho^2)$ is called the **purity**. It equals 1 for\n", - "pure states and reaches a minimum of $1/d$ (where $d$ is the Hilbert space\n", - "dimension) for the maximally mixed state $\\rho = I/d$.\n", - "\n", - "**Fidelity** $F = \\langle\\psi|\\rho|\\psi\\rangle$ measures how close a noisy\n", - "state $\\rho$ is to an ideal pure state $|\\psi\\rangle$. $F = 1$ means perfect,\n", - "$F = 1/d$ means no better than random.\n", - "\n", - "Let's create a Bell state using the density matrix simulator and verify it\n", - "is pure." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "cell-3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Bell circuit:\n", - "Density matrix (non-zero elements):\n", - " rho[|00>,|00>] = +0.5000\n", - " rho[|00>,|11>] = +0.5000\n", - " rho[|11>,|00>] = +0.5000\n", - " rho[|11>,|11>] = +0.5000\n", - "\n", - "Purity: 1.000000\n", - "Is pure (purity ~ 1.0): true\n", - "Fidelity vs ideal: 1.000000\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Build a Bell state: H(0), CNOT(0,1)\n", - "cBell, err := builder.New(\"bell\", 2).H(0).CNOT(0, 1).Build()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Bell circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(cBell))\n", - "\n", - "// Create a density matrix simulator (no noise)\n", - "dm := densitymatrix.New(2)\n", - "if err := dm.Evolve(cBell); err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "// Inspect the density matrix\n", - "rho := dm.DensityMatrix()\n", - "dim := 4 // 2^2\n", - "fmt.Println(\"Density matrix (non-zero elements):\")\n", - "for r := 0; r < dim; r++ {\n", - "\tfor c := 0; c < dim; c++ {\n", - "\t\tv := rho[r*dim+c]\n", - "\t\tif real(v)*real(v)+imag(v)*imag(v) > 1e-10 {\n", - "\t\t\tfmt.Printf(\" rho[|%02b>,|%02b>] = %+.4f\\n\", r, c, real(v))\n", - "\t\t}\n", - "\t}\n", - "}\n", - "\n", - "fmt.Printf(\"\\nPurity: %.6f\\n\", dm.Purity())\n", - "fmt.Printf(\"Is pure (purity ~ 1.0): %v\\n\", math.Abs(dm.Purity()-1.0) < 1e-10)\n", - "\n", - "// Get the ideal statevector for fidelity comparisons later\n", - "svSim := statevector.New(2)\n", - "if err := svSim.Evolve(cBell); err != nil {\n", - "\tpanic(err)\n", - "}\n", - "idealSV := svSim.StateVector()\n", - "fmt.Printf(\"Fidelity vs ideal: %.6f\\n\", dm.Fidelity(idealSV))" - ] - }, - { - "cell_type": "markdown", - "id": "cell-4", - "metadata": {}, - "source": [ - "## Noise Channels\n", - "\n", - "A **noise channel** describes how the environment corrupts a quantum state.\n", - "Mathematically, it is a completely positive, trace-preserving (CPTP) map\n", - "expressed via **Kraus operators** $\\{E_k\\}$:\n", - "\n", - "$$\\rho \\mapsto \\sum_k E_k \\, \\rho \\, E_k^\\dagger$$\n", - "\n", - "The trace-preservation condition $\\sum_k E_k^\\dagger E_k = I$ guarantees\n", - "probabilities still sum to 1 after the noise is applied.\n", - "\n", - "In Goqu, we attach noise channels to gates via a `NoiseModel`. After each\n", - "gate is applied, its associated noise channel is automatically applied to\n", - "the density matrix. Let's see how depolarizing noise degrades our Bell state." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "cell-5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== Ideal (noiseless) ===\n", - " Purity: 1.000000\n", - " Fidelity: 1.000000\n", - "\n", - "=== Noisy (1%% depolarizing on H, 2%% on CNOT) ===\n", - " Purity: 0.955656\n", - " Fidelity: 0.977476\n", - "\n", - "Noise reduces both purity and fidelity.\n", - "Purity < 1 means the state is mixed (no longer a pure quantum state).\n", - "Fidelity < 1 means the state has drifted from the ideal target.\n" - ] - } - ], - "source": [ - "%%\n", - "// Reconstruct cBell, idealSV, and dm from cell-3\n", - "cBell, _ := builder.New(\"bell\", 2).H(0).CNOT(0, 1).Build()\n", - "svRef := statevector.New(2)\n", - "svRef.Evolve(cBell)\n", - "idealSV := svRef.StateVector()\n", - "dm := densitymatrix.New(2)\n", - "dm.Evolve(cBell)\n", - "\n", - "// Create a noise model with depolarizing noise on H and CNOT gates\n", - "nm := noise.New()\n", - "nm.AddGateError(\"H\", noise.Depolarizing1Q(0.01))\n", - "nm.AddGateError(\"CNOT\", noise.Depolarizing2Q(0.02))\n", - "\n", - "// Simulate the Bell circuit with noise\n", - "dmNoisy := densitymatrix.New(2)\n", - "dmNoisy.WithNoise(nm)\n", - "if err := dmNoisy.Evolve(cBell); err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "// Compare ideal vs noisy\n", - "fmt.Println(\"=== Ideal (noiseless) ===\")\n", - "fmt.Printf(\" Purity: %.6f\\n\", dm.Purity())\n", - "fmt.Printf(\" Fidelity: %.6f\\n\", dm.Fidelity(idealSV))\n", - "\n", - "fmt.Println(\"\\n=== Noisy (1%% depolarizing on H, 2%% on CNOT) ===\")\n", - "fmt.Printf(\" Purity: %.6f\\n\", dmNoisy.Purity())\n", - "fmt.Printf(\" Fidelity: %.6f\\n\", dmNoisy.Fidelity(idealSV))\n", - "\n", - "fmt.Println(\"\\nNoise reduces both purity and fidelity.\")\n", - "fmt.Println(\"Purity < 1 means the state is mixed (no longer a pure quantum state).\")\n", - "fmt.Println(\"Fidelity < 1 means the state has drifted from the ideal target.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-6", - "metadata": {}, - "source": [ - "## Types of Noise Channels\n", - "\n", - "Different physical mechanisms produce different kinds of noise:\n", - "\n", - "| Channel | Physical origin | Effect |\n", - "|:---|:---|:---|\n", - "| **Depolarizing** | Isotropic noise; random Pauli errors (X, Y, Z) with equal probability | Shrinks the Bloch vector uniformly toward the origin |\n", - "| **Amplitude damping** | Energy relaxation (T1 decay); spontaneous emission | Drives $|1\\rangle \\to |0\\rangle$; the qubit loses energy |\n", - "| **Phase damping** | Pure dephasing (T2 without T1); frequency fluctuations | Destroys off-diagonal coherence; no population change |\n", - "| **Bit flip** | Random X error | Flips $|0\\rangle \\leftrightarrow |1\\rangle$ with probability $p$ |\n", - "| **Phase flip** | Random Z error | Flips phase of $|1\\rangle$ with probability $p$ |\n", - "\n", - "Let's apply each channel (with the same error parameter $p = 0.1$) to a\n", - "simple $H|0\\rangle = |+\\rangle$ circuit and compare their effects on purity\n", - "and fidelity." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "cell-7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Channel Purity Fidelity\n", - "------------------------------------------------------\n", - "Depolarizing(0.1) 0.875556 0.933333\n", - "AmplitudeDamping(0.1) 0.955000 0.974342\n", - "PhaseDamping(0.1) 0.950000 0.974342\n", - "BitFlip(0.1) 1.000000 1.000000\n", - "PhaseFlip(0.1) 0.820000 0.900000\n", - "\n", - "Note: Different channels affect the state differently.\n", - "Phase flip hurts |+> the most because |+> is a superposition in the Z basis.\n", - "Amplitude damping pushes toward |0>, which partially overlaps |+>.\n" - ] - } - ], - "source": [ - "%%\n", - "// Single-qubit H circuit: |0> -> |+>\n", - "cH, err := builder.New(\"h\", 1).H(0).Build()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "// Ideal statevector for fidelity reference\n", - "svIdeal := statevector.New(1)\n", - "if err := svIdeal.Evolve(cH); err != nil {\n", - "\tpanic(err)\n", - "}\n", - "idealPlus := svIdeal.StateVector()\n", - "\n", - "// Define channels to compare\n", - "type channelEntry struct {\n", - "\tname string\n", - "\tch noise.Channel\n", - "}\n", - "channels := []channelEntry{\n", - "\t{\"Depolarizing(0.1)\", noise.Depolarizing1Q(0.1)},\n", - "\t{\"AmplitudeDamping(0.1)\", noise.AmplitudeDamping(0.1)},\n", - "\t{\"PhaseDamping(0.1)\", noise.PhaseDamping(0.1)},\n", - "\t{\"BitFlip(0.1)\", noise.BitFlip(0.1)},\n", - "\t{\"PhaseFlip(0.1)\", noise.PhaseFlip(0.1)},\n", - "}\n", - "\n", - "fmt.Printf(\"%-28s %10s %10s\\n\", \"Channel\", \"Purity\", \"Fidelity\")\n", - "fmt.Println(\"------------------------------------------------------\")\n", - "\n", - "for _, entry := range channels {\n", - "\tnm := noise.New()\n", - "\tnm.AddGateError(\"H\", entry.ch)\n", - "\n", - "\tsim := densitymatrix.New(1)\n", - "\tsim.WithNoise(nm)\n", - "\tif err := sim.Evolve(cH); err != nil {\n", - "\t\tpanic(err)\n", - "\t}\n", - "\n", - "\tfmt.Printf(\"%-28s %10.6f %10.6f\\n\", entry.name, sim.Purity(), sim.Fidelity(idealPlus))\n", - "}\n", - "\n", - "fmt.Println(\"\\nNote: Different channels affect the state differently.\")\n", - "fmt.Println(\"Phase flip hurts |+> the most because |+> is a superposition in the Z basis.\")\n", - "fmt.Println(\"Amplitude damping pushes toward |0>, which partially overlaps |+>.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-8", - "metadata": {}, - "source": [ - "## Increasing Noise Strength\n", - "\n", - "How does fidelity degrade as we increase the depolarizing noise parameter $p$?\n", - "For a single H gate with depolarizing noise, as $p$ goes from 0 to the\n", - "maximum mixing value, the output state smoothly transitions from the ideal\n", - "$|+\\rangle$ to the maximally mixed state $I/2$.\n", - "\n", - "Let's sweep $p$ from 0 to 0.5 and observe the fidelity curve." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "cell-9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " p Purity Fidelity\n", - "-------------------------------\n", - " 0.00 1.000000 1.000000\n", - " 0.05 0.935556 0.966667\n", - " 0.10 0.875556 0.933333\n", - " 0.15 0.820000 0.900000\n", - " 0.20 0.768889 0.866667\n", - " 0.25 0.722222 0.833333\n", - " 0.30 0.680000 0.800000\n", - " 0.35 0.642222 0.766667\n", - " 0.40 0.608889 0.733333\n", - " 0.45 0.580000 0.700000\n", - " 0.50 0.555556 0.666667\n", - "\n", - "As p increases, both purity and fidelity decrease.\n", - "At p=0.75 (not shown), the output would be completely mixed: purity = 0.5, fidelity = 0.5.\n" - ] - } - ], - "source": [ - "%%\n", - "// Reconstruct cH and idealPlus from cell-7\n", - "cH, _ := builder.New(\"h\", 1).H(0).Build()\n", - "svH := statevector.New(1)\n", - "svH.Evolve(cH)\n", - "idealPlus := svH.StateVector()\n", - "\n", - "// Sweep depolarizing noise from p=0 to p=0.5\n", - "fmt.Printf(\"%6s %10s %10s\\n\", \"p\", \"Purity\", \"Fidelity\")\n", - "fmt.Println(\"-------------------------------\")\n", - "\n", - "for i := 0; i <= 10; i++ {\n", - "\tp := float64(i) * 0.05\n", - "\n", - "\tnm := noise.New()\n", - "\tnm.AddGateError(\"H\", noise.Depolarizing1Q(p))\n", - "\n", - "\tsim := densitymatrix.New(1)\n", - "\tsim.WithNoise(nm)\n", - "\tif err := sim.Evolve(cH); err != nil {\n", - "\t\tpanic(err)\n", - "\t}\n", - "\n", - "\tfmt.Printf(\"%6.2f %10.6f %10.6f\\n\", p, sim.Purity(), sim.Fidelity(idealPlus))\n", - "}\n", - "\n", - "fmt.Println(\"\\nAs p increases, both purity and fidelity decrease.\")\n", - "fmt.Println(\"At p=0.75 (not shown), the output would be completely mixed: purity = 0.5, fidelity = 0.5.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-10", - "metadata": {}, - "source": [ - "## Density Matrix Visualization\n", - "\n", - "The **state city plot** is an isometric 3D bar chart of the density matrix\n", - "elements. Each bar shows the real or imaginary part of $\\rho_{ij}$. For a\n", - "pure Bell state, the density matrix has four non-zero entries (the corners of\n", - "the $|00\\rangle$/$|11\\rangle$ subspace). Noise smears this structure, adding\n", - "weight to other elements and reducing the height of the ideal peaks.\n", - "\n", - "Let's visualize the ideal Bell state and a noisy version side by side." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "cell-11", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "Ideal Bell State\n", - "Re(ρ)\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "|00⟩\n", - "|00⟩\n", - "|01⟩\n", - "|01⟩\n", - "|10⟩\n", - "|10⟩\n", - "|11⟩\n", - "|11⟩\n", - "Im(ρ)\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "|00⟩\n", - "|00⟩\n", - "|01⟩\n", - "|01⟩\n", - "|10⟩\n", - "|10⟩\n", - "|11⟩\n", - "|11⟩\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "Noisy Bell State (5%/10% depolarizing)\n", - "Re(ρ)\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "|00⟩\n", - "|00⟩\n", - "|01⟩\n", - "|01⟩\n", - "|10⟩\n", - "|10⟩\n", - "|11⟩\n", - "|11⟩\n", - "Im(ρ)\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "|00⟩\n", - "|00⟩\n", - "|01⟩\n", - "|01⟩\n", - "|10⟩\n", - "|10⟩\n", - "|11⟩\n", - "|11⟩\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Ideal -- Purity: 1.0000, Fidelity: 1.0000\n", - "Noisy -- Purity: 0.7971, Fidelity: 0.8902\n" - ] - } - ], - "source": [ - "%%\n", - "// Reconstruct cBell and idealSV from cell-3\n", - "cBell, _ := builder.New(\"bell\", 2).H(0).CNOT(0, 1).Build()\n", - "svRef := statevector.New(2)\n", - "svRef.Evolve(cBell)\n", - "idealSV := svRef.StateVector()\n", - "\n", - "// Ideal Bell state density matrix\n", - "dmIdeal := densitymatrix.New(2)\n", - "if err := dmIdeal.Evolve(cBell); err != nil {\n", - "\tpanic(err)\n", - "}\n", - "rhoIdeal := dmIdeal.DensityMatrix()\n", - "\n", - "// Noisy Bell state (5% depolarizing on all gates)\n", - "nmViz := noise.New()\n", - "nmViz.AddGateError(\"H\", noise.Depolarizing1Q(0.05))\n", - "nmViz.AddGateError(\"CNOT\", noise.Depolarizing2Q(0.10))\n", - "\n", - "dmNoisyViz := densitymatrix.New(2)\n", - "dmNoisyViz.WithNoise(nmViz)\n", - "if err := dmNoisyViz.Evolve(cBell); err != nil {\n", - "\tpanic(err)\n", - "}\n", - "rhoNoisy := dmNoisyViz.DensityMatrix()\n", - "\n", - "// Display state city plots\n", - "svgIdeal := viz.StateCity(rhoIdeal, 4, viz.WithTitle(\"Ideal Bell State\"))\n", - "gonbui.DisplayHTML(svgIdeal)\n", - "\n", - "svgNoisy := viz.StateCity(rhoNoisy, 4, viz.WithTitle(\"Noisy Bell State (5%/10% depolarizing)\"))\n", - "gonbui.DisplayHTML(svgNoisy)\n", - "\n", - "fmt.Printf(\"Ideal -- Purity: %.4f, Fidelity: %.4f\\n\", dmIdeal.Purity(), dmIdeal.Fidelity(idealSV))\n", - "fmt.Printf(\"Noisy -- Purity: %.4f, Fidelity: %.4f\\n\", dmNoisyViz.Purity(), dmNoisyViz.Fidelity(idealSV))" - ] - }, - { - "cell_type": "markdown", - "id": "cell-12", - "metadata": {}, - "source": [ - "## Per-Qubit Noise and Readout Errors\n", - "\n", - "Real devices do not have uniform noise across all qubits. Some qubits are\n", - "noisier than others due to fabrication variations. Goqu's noise model\n", - "supports **qubit-specific gate errors** and **readout errors**.\n", - "\n", - "The resolution order for noise lookup is:\n", - "1. Gate name + specific qubits (most specific)\n", - "2. Gate name (any qubits)\n", - "3. Qubit count default (least specific)\n", - "\n", - "**Readout errors** model classical measurement mistakes: $P(1|0)$ is the\n", - "probability of reading 1 when the true state is 0, and $P(0|1)$ is the\n", - "probability of reading 0 when the true state is 1." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "cell-13", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Per-qubit noise model:\n", - " H on qubit 0: 5% depolarizing (qubit-specific override)\n", - " H on qubit 1: 1% depolarizing (gate-level default)\n", - " CNOT: 2% depolarizing\n", - " Readout q0: P(1|0)=0.02, P(0|1)=0.03\n", - " Readout q1: P(1|0)=0.01, P(0|1)=0.01\n", - "\n", - "Purity: 0.906617\n", - "Fidelity: 0.951378\n", - "\n", - "Measurement counts (1000 shots, includes readout error):\n", - " |00> : 513\n", - " |10> : 18\n", - " |01> : 20\n", - " |11> : 449\n", - "\n", - "Note: |01> and |10> appear due to both gate noise and readout errors.\n" - ] - } - ], - "source": [ - "%%\n", - "// Reconstruct cBell and idealSV from cell-3\n", - "cBell, _ := builder.New(\"bell\", 2).H(0).CNOT(0, 1).Build()\n", - "svRef := statevector.New(2)\n", - "svRef.Evolve(cBell)\n", - "idealSV := svRef.StateVector()\n", - "\n", - "// Per-qubit noise: qubit 0 is noisier than qubit 1\n", - "nmPerQubit := noise.New()\n", - "\n", - "// Default H noise: 1%\n", - "nmPerQubit.AddGateError(\"H\", noise.Depolarizing1Q(0.01))\n", - "\n", - "// But qubit 0 has 5% noise on H (overrides the 1% default for this qubit)\n", - "nmPerQubit.AddGateQubitError(\"H\", []int{0}, noise.Depolarizing1Q(0.05))\n", - "\n", - "// CNOT noise\n", - "nmPerQubit.AddGateError(\"CNOT\", noise.Depolarizing2Q(0.02))\n", - "\n", - "// Readout errors: qubit 0 has 2% P(1|0) and 3% P(0|1)\n", - "nmPerQubit.AddReadoutError(0, noise.NewReadoutError(0.02, 0.03))\n", - "nmPerQubit.AddReadoutError(1, noise.NewReadoutError(0.01, 0.01))\n", - "\n", - "// Evolve with per-qubit noise\n", - "dmPerQubit := densitymatrix.New(2)\n", - "dmPerQubit.WithNoise(nmPerQubit)\n", - "if err := dmPerQubit.Evolve(cBell); err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Per-qubit noise model:\")\n", - "fmt.Println(\" H on qubit 0: 5% depolarizing (qubit-specific override)\")\n", - "fmt.Println(\" H on qubit 1: 1% depolarizing (gate-level default)\")\n", - "fmt.Println(\" CNOT: 2% depolarizing\")\n", - "fmt.Println(\" Readout q0: P(1|0)=0.02, P(0|1)=0.03\")\n", - "fmt.Println(\" Readout q1: P(1|0)=0.01, P(0|1)=0.01\")\n", - "fmt.Printf(\"\\nPurity: %.6f\\n\", dmPerQubit.Purity())\n", - "fmt.Printf(\"Fidelity: %.6f\\n\", dmPerQubit.Fidelity(idealSV))\n", - "\n", - "// Sample measurements with readout error\n", - "cBellMeas, _ := builder.New(\"bell-meas\", 2).H(0).CNOT(0, 1).MeasureAll().Build()\n", - "counts, err := dmPerQubit.Run(cBellMeas, 1000)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"\\nMeasurement counts (1000 shots, includes readout error):\")\n", - "for outcome, count := range counts {\n", - "\tfmt.Printf(\" |%s> : %d\\n\", outcome, count)\n", - "}\n", - "fmt.Println(\"\\nNote: |01> and |10> appear due to both gate noise and readout errors.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-14", - "metadata": {}, - "source": [ - "## Custom Noise Channels\n", - "\n", - "Sometimes the built-in channels are not enough. You may want to model a\n", - "specific error process from a hardware characterization experiment. Goqu\n", - "lets you define a **custom channel** from arbitrary Kraus operators, as long\n", - "as they satisfy the trace-preservation condition $\\sum_k E_k^\\dagger E_k = I$.\n", - "\n", - "Let's build a custom asymmetric depolarizing channel where X errors are more\n", - "likely than Y or Z errors." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "cell-15", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Custom asymmetric depolarizing channel:\n", - " P(no error) = 0.90, P(X) = 0.06, P(Y) = 0.02, P(Z) = 0.02\n", - "\n", - "Purity: 0.923200\n", - "Fidelity: 0.960000\n", - "\n", - "Comparison with symmetric Depolarizing1Q(0.10):\n", - " Symmetric purity: 0.875556\n", - " Symmetric fidelity: 0.933333\n", - "\n", - "Different error distributions produce different fidelity even at the same total error rate.\n" - ] - } - ], - "source": [ - "%%\n", - "// Reconstruct cH and idealPlus from cell-7\n", - "cH, _ := builder.New(\"h\", 1).H(0).Build()\n", - "svH := statevector.New(1)\n", - "svH.Evolve(cH)\n", - "idealPlus := svH.StateVector()\n", - "\n", - "// Custom asymmetric depolarizing channel:\n", - "// P(X error) = 0.06, P(Y error) = 0.02, P(Z error) = 0.02\n", - "// P(no error) = 1 - 0.06 - 0.02 - 0.02 = 0.90\n", - "pI := math.Sqrt(0.90)\n", - "pX := math.Sqrt(0.06)\n", - "pY := math.Sqrt(0.02)\n", - "pZ := math.Sqrt(0.02)\n", - "\n", - "customCh := noise.MustCustom(\"asymmetric_depol\", 1, [][]complex128{\n", - "\t// sqrt(0.90) * I\n", - "\t{complex(pI, 0), 0, 0, complex(pI, 0)},\n", - "\t// sqrt(0.06) * X\n", - "\t{0, complex(pX, 0), complex(pX, 0), 0},\n", - "\t// sqrt(0.02) * Y\n", - "\t{0, complex(0, -pY), complex(0, pY), 0},\n", - "\t// sqrt(0.02) * Z\n", - "\t{complex(pZ, 0), 0, 0, complex(-pZ, 0)},\n", - "})\n", - "\n", - "// Apply to H gate\n", - "nmCustom := noise.New()\n", - "nmCustom.AddGateError(\"H\", customCh)\n", - "\n", - "dmCustom := densitymatrix.New(1)\n", - "dmCustom.WithNoise(nmCustom)\n", - "if err := dmCustom.Evolve(cH); err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Custom asymmetric depolarizing channel:\")\n", - "fmt.Println(\" P(no error) = 0.90, P(X) = 0.06, P(Y) = 0.02, P(Z) = 0.02\")\n", - "fmt.Printf(\"\\nPurity: %.6f\\n\", dmCustom.Purity())\n", - "fmt.Printf(\"Fidelity: %.6f\\n\", dmCustom.Fidelity(idealPlus))\n", - "\n", - "// Compare with symmetric depolarizing at same total error rate (p=0.10)\n", - "nmSym := noise.New()\n", - "nmSym.AddGateError(\"H\", noise.Depolarizing1Q(0.10))\n", - "dmSym := densitymatrix.New(1)\n", - "dmSym.WithNoise(nmSym)\n", - "if err := dmSym.Evolve(cH); err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"\\nComparison with symmetric Depolarizing1Q(0.10):\\n\")\n", - "fmt.Printf(\" Symmetric purity: %.6f\\n\", dmSym.Purity())\n", - "fmt.Printf(\" Symmetric fidelity: %.6f\\n\", dmSym.Fidelity(idealPlus))\n", - "fmt.Println(\"\\nDifferent error distributions produce different fidelity even at the same total error rate.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-16", - "metadata": {}, - "source": [ - "## Thermal Relaxation (T1/T2)\n", - "\n", - "The most physically realistic single-qubit noise model combines two time\n", - "scales:\n", - "\n", - "- **T1** (energy relaxation time): how quickly the qubit decays from $|1\\rangle$\n", - " to $|0\\rangle$ via spontaneous emission. This is amplitude damping.\n", - "- **T2** (dephasing time): how quickly quantum coherence (off-diagonal elements\n", - " of $\\rho$) decays. This includes both T1 effects and pure dephasing.\n", - "\n", - "The physical constraint is $T_2 \\leq 2 T_1$ (you cannot dephase slower than\n", - "the T1 limit). When $T_2 = 2 T_1$, all dephasing comes from energy relaxation\n", - "alone. When $T_2 < 2 T_1$, there is additional pure dephasing.\n", - "\n", - "The `ThermalRelaxation(t1, t2, time)` channel composes amplitude damping\n", - "with residual phase damping to match the target T1 and T2 coherence times\n", - "for a gate of the given duration." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "cell-17", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Thermal Relaxation: T1=100ns, T2=60ns, gate=20ns\n", - " Purity: 0.773138\n", - " Fidelity: 0.858266\n", - "\n", - " Gate (ns) Purity Fidelity\n", - "-----------------------------------\n", - " 0 1.000000 1.000000\n", - " 5 0.924430 0.960022\n", - " 10 0.862794 0.923241\n", - " 20 0.773138 0.858266\n", - " 40 0.686143 0.756709\n", - " 60 0.669453 0.683940\n", - " 80 0.686361 0.631799\n", - " 100 0.717625 0.594438\n", - "\n", - "Longer gate times mean more decoherence.\n", - "This is why fast gates are essential for quantum computing.\n" - ] - } - ], - "source": [ - "%%\n", - "// Reconstruct cH and idealPlus from cell-7\n", - "cH, _ := builder.New(\"h\", 1).H(0).Build()\n", - "svH := statevector.New(1)\n", - "svH.Evolve(cH)\n", - "idealPlus := svH.StateVector()\n", - "\n", - "// Model thermal relaxation with T1=100ns, T2=60ns, gate time=20ns\n", - "t1 := 100.0 // T1 in nanoseconds\n", - "t2 := 60.0 // T2 in nanoseconds (T2 <= 2*T1)\n", - "gateTime := 20.0 // gate duration in nanoseconds\n", - "\n", - "trCh := noise.ThermalRelaxation(t1, t2, gateTime)\n", - "\n", - "nmTR := noise.New()\n", - "nmTR.AddGateError(\"H\", trCh)\n", - "\n", - "dmTR := densitymatrix.New(1)\n", - "dmTR.WithNoise(nmTR)\n", - "if err := dmTR.Evolve(cH); err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"Thermal Relaxation: T1=%.0fns, T2=%.0fns, gate=%.0fns\\n\", t1, t2, gateTime)\n", - "fmt.Printf(\" Purity: %.6f\\n\", dmTR.Purity())\n", - "fmt.Printf(\" Fidelity: %.6f\\n\", dmTR.Fidelity(idealPlus))\n", - "\n", - "// Sweep different gate durations to see decoherence over time\n", - "fmt.Printf(\"\\n%10s %10s %10s\\n\", \"Gate (ns)\", \"Purity\", \"Fidelity\")\n", - "fmt.Println(\"-----------------------------------\")\n", - "\n", - "for _, gt := range []float64{0, 5, 10, 20, 40, 60, 80, 100} {\n", - "\tch := noise.ThermalRelaxation(t1, t2, gt)\n", - "\tnm := noise.New()\n", - "\tnm.AddGateError(\"H\", ch)\n", - "\n", - "\tsim := densitymatrix.New(1)\n", - "\tsim.WithNoise(nm)\n", - "\tif err := sim.Evolve(cH); err != nil {\n", - "\t\tpanic(err)\n", - "\t}\n", - "\n", - "\tfmt.Printf(\"%10.0f %10.6f %10.6f\\n\", gt, sim.Purity(), sim.Fidelity(idealPlus))\n", - "}\n", - "\n", - "fmt.Println(\"\\nLonger gate times mean more decoherence.\")\n", - "fmt.Println(\"This is why fast gates are essential for quantum computing.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-18", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** What happens to the purity of a Bell state as depolarizing noise strength increases from $p = 0$ to $p = 0.75$?\n", - "\n", - "**Pause and predict** before running the next cell.\n", - "\n", - "*Hint: Purity is $\\text{Tr}(\\rho^2)$. A pure state has purity 1. A maximally mixed 2-qubit state has purity $1/4$. How does depolarizing noise interpolate between these extremes?*" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "cell-19", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " p Purity Expected\n", - "-------------------------------\n", - " 0.00 1.000000 1.000000\n", - " 0.05 0.935556 0.935556\n", - " 0.10 0.875556 0.875556\n", - " 0.15 0.820000 0.820000\n", - " 0.20 0.768889 0.768889\n", - " 0.25 0.722222 0.722222\n", - " 0.30 0.680000 0.680000\n", - " 0.35 0.642222 0.642222\n", - " 0.40 0.608889 0.608889\n", - " 0.45 0.580000 0.580000\n", - " 0.50 0.555556 0.555556\n", - " 0.55 0.535556 0.535556\n", - " 0.60 0.520000 0.520000\n", - " 0.65 0.508889 0.508889\n", - " 0.70 0.502222 0.502222\n", - " 0.75 0.500000 0.500000\n", - "\n", - "Prediction confirmed: purity decreases smoothly from 1.0 to 0.5.\n", - "The 'Expected' column uses the analytical formula for depolarizing purity.\n" - ] - } - ], - "source": [ - "%%\n", - "// Reconstruct cH from cell-7\n", - "cH, _ := builder.New(\"h\", 1).H(0).Build()\n", - "\n", - "// Verify: purity vs depolarizing noise from p=0 to p=0.75\n", - "fmt.Printf(\"%6s %10s %10s\\n\", \"p\", \"Purity\", \"Expected\")\n", - "fmt.Println(\"-------------------------------\")\n", - "\n", - "for i := 0; i <= 15; i++ {\n", - "\tp := float64(i) * 0.05\n", - "\n", - "\tnm := noise.New()\n", - "\tnm.AddGateError(\"H\", noise.Depolarizing1Q(p))\n", - "\n", - "\tsim := densitymatrix.New(1)\n", - "\tsim.WithNoise(nm)\n", - "\tif err := sim.Evolve(cH); err != nil {\n", - "\t\tpanic(err)\n", - "\t}\n", - "\n", - "\t// Depolarizing channel: rho -> (1-p)*rho + (p/3)(X*rho*X + Y*rho*Y + Z*rho*Z)\n", - "\t// For a pure input, purity = (1 - 4p/3 + 16p^2/9) ... decreases to 0.5 at p=0.75\n", - "\texpected := 0.5 + 0.5*math.Pow(1-4.0*p/3.0, 2)\n", - "\n", - "\tfmt.Printf(\"%6.2f %10.6f %10.6f\\n\", p, sim.Purity(), expected)\n", - "}\n", - "\n", - "fmt.Println(\"\\nPrediction confirmed: purity decreases smoothly from 1.0 to 0.5.\")\n", - "fmt.Println(\"The 'Expected' column uses the analytical formula for depolarizing purity.\")" - ] - }, - { - "cell_type": "markdown", - "id": "9sosg659cib", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. What is the difference between a pure state and a mixed state in terms of the density matrix?\n", - "2. Why must T2 <= 2*T1 for any physical qubit?\n", - "3. True or false: Depolarizing noise always drives a qubit toward the |0⟩ state. Explain." - ] - }, - { - "cell_type": "markdown", - "id": "cell-20", - "metadata": {}, - "source": [ - "## Exercises\n", - "\n", - "### Exercise 1: Model T1 Decay on a Single Qubit\n", - "\n", - "Prepare the $|1\\rangle$ state (apply X to $|0\\rangle$), then sweep the gate\n", - "duration from 0 to $2 \\times T_1$ using `ThermalRelaxation`. The $|1\\rangle$\n", - "population should decay exponentially as $e^{-t/T_1}$. Verify this by\n", - "comparing the fidelity to $|1\\rangle$ at each time step." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "cell-21", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the parameters!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: T1 decay of |1> state\n", - "// Prepare |1> = X|0>, then sweep gate duration from 0 to 2*T1\n", - "// and observe fidelity decaying as exp(-t/T1).\n", - "//\n", - "// TODO: Build a circuit that prepares |1> (apply X to qubit 0)\n", - "// cX, err := builder.New(\"x\", 1).X(0).Build()\n", - "//\n", - "// TODO: Get the ideal |1> statevector for fidelity comparison\n", - "// svOne := statevector.New(1)\n", - "// svOne.Evolve(cX)\n", - "// idealOne := svOne.StateVector()\n", - "//\n", - "// TODO: Set T1 and T2 parameters (try T1=100ns, T2=100ns)\n", - "// t1Ex := ???\n", - "// t2Ex := ???\n", - "//\n", - "// TODO: Loop over gate times from 0 to 200ns\n", - "// For each gate time:\n", - "// 1. Create a ThermalRelaxation channel: noise.ThermalRelaxation(t1Ex, t2Ex, gt)\n", - "// 2. Attach it to the X gate in a noise model\n", - "// 3. Simulate with densitymatrix and compute fidelity to |1>\n", - "// 4. Compare with exp(-gt/t1Ex)\n", - "//\n", - "// for _, gt := range []float64{0, 10, 20, 40, 60, 80, 100, 150, 200} {\n", - "// ch := noise.ThermalRelaxation(???, ???, ???)\n", - "// nm := noise.New()\n", - "// nm.AddGateError(\"X\", ch)\n", - "// sim := densitymatrix.New(1)\n", - "// sim.WithNoise(nm)\n", - "// sim.Evolve(cX)\n", - "// fid := sim.Fidelity(idealOne)\n", - "// expDecay := math.Exp(-gt / t1Ex)\n", - "// fmt.Printf(\"t=%3.0fns fidelity=%.4f exp(-t/T1)=%.4f\\n\", gt, fid, expDecay)\n", - "// }\n", - "fmt.Println(\"Uncomment the code above and fill in the parameters!\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-22", - "metadata": {}, - "source": [ - "### Exercise 2: Build a Custom Noise Channel\n", - "\n", - "Create a custom **dephasing + bit-flip** channel that applies:\n", - "- X error with probability 0.05\n", - "- Z error with probability 0.03\n", - "- No error with probability 0.92\n", - "\n", - "Use `noise.MustCustom` and apply it to an H gate, then compare the result\n", - "with the built-in `BitFlip(0.05)` alone." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "cell-23", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the probabilities!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: Custom combined bit-flip + phase-flip channel\n", - "// Create a channel with: P(no error) = 0.92, P(X) = 0.05, P(Z) = 0.03\n", - "//\n", - "// TODO: Compute Kraus operator coefficients (square roots of probabilities)\n", - "// sI := math.Sqrt(???)\n", - "// sX := math.Sqrt(???)\n", - "// sZ := math.Sqrt(???)\n", - "//\n", - "// TODO: Build the custom channel using noise.MustCustom\n", - "// Kraus operators: sqrt(p_I)*I, sqrt(p_X)*X, sqrt(p_Z)*Z\n", - "// customBPCh := noise.MustCustom(\"bitflip_phaseflip\", 1, [][]complex128{\n", - "// // sqrt(0.92) * I\n", - "// {complex(sI, 0), 0, 0, complex(sI, 0)},\n", - "// // sqrt(0.05) * X\n", - "// {0, complex(sX, 0), complex(sX, 0), 0},\n", - "// // sqrt(0.03) * Z\n", - "// {complex(sZ, 0), 0, 0, complex(-sZ, 0)},\n", - "// })\n", - "//\n", - "// TODO: Apply the custom channel to an H gate and simulate\n", - "// TODO: Also simulate with just BitFlip(0.05) for comparison\n", - "// TODO: Print purity and fidelity for both, and explain why\n", - "// the custom channel has lower fidelity (additional Z errors)\n", - "fmt.Println(\"Uncomment the code above and fill in the probabilities!\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-24", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "\n", - "1. **Density matrices** generalize statevectors to describe mixed (noisy)\n", - " states. A pure state has purity $\\text{Tr}(\\rho^2) = 1$; noise reduces\n", - " purity below 1.\n", - "\n", - "2. **Noise channels** are CPTP maps described by Kraus operators\n", - " $\\{E_k\\}$ satisfying $\\sum_k E_k^\\dagger E_k = I$. They model the\n", - " interaction between a qubit and its environment.\n", - "\n", - "3. **Depolarizing noise** is the simplest error model (isotropic random\n", - " Pauli errors). **Amplitude damping** models T1 energy relaxation.\n", - " **Phase damping** models pure T2 dephasing. **Bit flip** and **phase flip**\n", - " model single Pauli errors.\n", - "\n", - "4. **T1 and T2 times** are the key hardware parameters. T1 limits how long\n", - " an excited qubit survives. T2 limits how long superpositions remain\n", - " coherent. Longer gate times mean more decoherence.\n", - "\n", - "5. **Fidelity** $F = \\langle\\psi|\\rho|\\psi\\rangle$ measures how close a noisy\n", - " state is to the ideal. It degrades smoothly with noise strength.\n", - "\n", - "6. **Custom channels** let you model arbitrary error processes from hardware\n", - " characterization data, as long as the Kraus operators are trace-preserving.\n", - "\n", - "7. **Quantum error correction cannot clone** -- the no-cloning theorem\n", - " forces fundamentally different strategies from classical EC. This is\n", - " why quantum error correction requires encoding logical qubits into\n", - " entangled states of many physical qubits." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/11b-quantum-error-correction.ipynb b/notebooks/11b-quantum-error-correction.ipynb deleted file mode 100644 index 02c57cf..0000000 --- a/notebooks/11b-quantum-error-correction.ipynb +++ /dev/null @@ -1,1642 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "cell-0", - "metadata": {}, - "source": [ - "# 11b -- Quantum Error Correction\n", - "\n", - "**Prerequisites:** Notebook 11. Familiarity with noise channels and density matrices.\n", - "\n", - "Quantum error correction (QEC) is the path to **fault-tolerant quantum\n", - "computing**. In notebook 11 we saw how noise corrupts quantum states, and\n", - "in notebook 15 we will explore error *mitigation* -- classical post-processing\n", - "tricks that improve noisy expectation values. But mitigation is a bandage,\n", - "not a cure: it cannot fix individual shots or scale to arbitrary circuit\n", - "depth.\n", - "\n", - "QEC is the cure. It encodes a single **logical qubit** into several\n", - "**physical qubits** in such a way that errors can be detected and\n", - "corrected *during the computation* -- without ever learning (or\n", - "disturbing) the encoded quantum information.\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Describe** why quantum error correction is fundamentally harder than classical error correction.\n", - "2. **Implement** the 3-qubit bit-flip and phase-flip repetition codes.\n", - "3. **Explain** syndrome measurement and how it detects errors without disturbing the encoded state.\n", - "4. **Demonstrate** the Shor 9-qubit code that corrects arbitrary single-qubit errors.\n", - "\n", - "In this notebook we will:\n", - "\n", - "1. Understand why naive classical strategies fail for quantum states.\n", - "2. Build the **3-qubit bit-flip code** -- the simplest QEC code.\n", - "3. Perform **syndrome measurement** to detect errors without collapsing the logical qubit.\n", - "4. Build the **3-qubit phase-flip code** using the Hadamard basis.\n", - "5. Combine both into the **Shor 9-qubit code** that corrects arbitrary single-qubit errors.\n", - "6. Preview the **stabilizer formalism** and connect to Clifford simulation (notebook 16).\n", - "\n", - "### Misconception: \"Just copy the qubit 3 times like a classical repetition code\"\n", - "\n", - "Classical error correction works by copying bits: store three copies,\n", - "take a majority vote. But the **no-cloning theorem** forbids copying an\n", - "unknown quantum state. And even if we could copy, **measurement destroys\n", - "superposition** -- we cannot peek at individual qubits to check for\n", - "errors without collapsing the encoded state. Quantum error correction\n", - "must extract error information (the *syndrome*) without learning\n", - "anything about the encoded data. This is a fundamentally harder problem." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "cell-1", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"fmt\"\n", - "\t\"math\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/circuit/gate\"\n", - "\t\"github.com/splch/goqu/sim/clifford\"\n", - "\t\"github.com/splch/goqu/sim/densitymatrix\"\n", - "\t\"github.com/splch/goqu/sim/noise\"\n", - "\t\"github.com/splch/goqu/sim/statevector\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-2", - "metadata": {}, - "source": [ - "## Why Quantum Error Correction Is Hard\n", - "\n", - "Classical error correction is straightforward: copy a bit three times\n", - "($0 \\to 000$, $1 \\to 111$), and if one bit flips, majority vote\n", - "recovers the original. Two things make this impossible for qubits:\n", - "\n", - "1. **The no-cloning theorem.** There is no quantum operation that takes\n", - " an unknown state $|\\psi\\rangle$ and produces $|\\psi\\rangle \\otimes |\\psi\\rangle$.\n", - " If we try to \"copy\" using CNOT, we get *entanglement*, not independent copies.\n", - "\n", - "2. **Measurement collapses the state.** In classical EC, we can freely\n", - " inspect each bit to find the error. In quantum EC, measuring a qubit\n", - " destroys its superposition. We need a way to extract error information\n", - " (the **syndrome**) without learning anything about the encoded state.\n", - "\n", - "Let's see the no-cloning theorem in action. We will try to \"copy\"\n", - "$|\\psi\\rangle = \\alpha|0\\rangle + \\beta|1\\rangle$ by applying CNOT\n", - "with the data qubit as control and a fresh $|0\\rangle$ as target." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "cell-3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Naive cloning attempt: CNOT(data, target)\n", - "Result statevector:\n", - " |00> : (0.8660+0.0000i) (prob = 0.7500)\n", - " |11> : (0.5000+0.0000i) (prob = 0.2500)\n", - "\n", - "If cloning worked, we would see amplitudes on |01> and |10>.\n", - "Instead, CNOT creates the entangled state cos(pi/6)|00> + sin(pi/6)|11>.\n", - "This is entanglement, not copying. The no-cloning theorem holds.\n", - "\n", - "But wait -- this entangled encoding is actually USEFUL!\n", - "It is the starting point for quantum error correction.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "RY(pi/3)\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Attempt to \"clone\" |psi> = RY(pi/3)|0> using CNOT.\n", - "// If cloning worked, we'd get |psi> x |psi>.\n", - "// Instead, CNOT creates an entangled state.\n", - "\n", - "cClone, err := builder.New(\"clone-attempt\", 2).\n", - "\tRY(math.Pi/3, 0). // Prepare |psi> = cos(pi/6)|0> + sin(pi/6)|1>\n", - "\tCNOT(0, 1). // \"Copy\" attempt\n", - "\tBuild()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Naive cloning attempt: CNOT(data, target)\")\n", - "gonbui.DisplayHTML(draw.SVG(cClone))\n", - "\n", - "sim := statevector.New(2)\n", - "if err := sim.Evolve(cClone); err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "sv := sim.StateVector()\n", - "fmt.Println(\"Result statevector:\")\n", - "for i, amp := range sv {\n", - "\tp := real(amp)*real(amp) + imag(amp)*imag(amp)\n", - "\tif p > 1e-10 {\n", - "\t\tfmt.Printf(\" |%02b> : %.4f (prob = %.4f)\\n\", i, amp, p)\n", - "\t}\n", - "}\n", - "\n", - "// If cloning worked, we'd have |psi> x |psi> = (cos^2)|00> + cos*sin|01> + cos*sin|10> + sin^2|11>\n", - "// Instead we get an ENTANGLED state: cos|00> + sin|11>\n", - "fmt.Println(\"\\nIf cloning worked, we would see amplitudes on |01> and |10>.\")\n", - "fmt.Println(\"Instead, CNOT creates the entangled state cos(pi/6)|00> + sin(pi/6)|11>.\")\n", - "fmt.Println(\"This is entanglement, not copying. The no-cloning theorem holds.\")\n", - "fmt.Println(\"\\nBut wait -- this entangled encoding is actually USEFUL!\")\n", - "fmt.Println(\"It is the starting point for quantum error correction.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-4", - "metadata": {}, - "source": [ - "## The 3-Qubit Bit-Flip Code\n", - "\n", - "The simplest quantum error-correcting code protects against **bit-flip\n", - "errors** (X errors). It uses three physical qubits to encode one logical\n", - "qubit.\n", - "\n", - "### Encoding\n", - "\n", - "We encode the logical states as:\n", - "\n", - "$$|0\\rangle_L = |000\\rangle, \\qquad |1\\rangle_L = |111\\rangle$$\n", - "\n", - "A general state $|\\psi\\rangle = \\alpha|0\\rangle + \\beta|1\\rangle$ becomes:\n", - "\n", - "$$|\\psi\\rangle_L = \\alpha|000\\rangle + \\beta|111\\rangle$$\n", - "\n", - "The encoding circuit is simple: apply CNOT from the data qubit to two\n", - "ancilla qubits initialized to $|0\\rangle$:\n", - "\n", - "```\n", - "q0 (data): --|-----*-----*--\n", - "q1 (ancilla): --|-----|--X--|--\n", - "q2 (ancilla): --|-----|-----|--X--\n", - " CNOT CNOT\n", - "```\n", - "\n", - "This creates entanglement, not copies. The three qubits are in a\n", - "joint state that cannot be factored -- and that is exactly what\n", - "protects the information.\n", - "\n", - "### Error and Syndrome Measurement\n", - "\n", - "Suppose a bit-flip error (X gate) hits one of the three data qubits.\n", - "We need to figure out *which* qubit was flipped without measuring the\n", - "data qubits directly (that would destroy the superposition).\n", - "\n", - "The trick is **syndrome measurement**: we use two ancilla qubits to\n", - "measure the *parity* of pairs of data qubits. The parity tells us\n", - "whether two qubits agree or disagree, without revealing their values:\n", - "\n", - "| Syndrome (s1 s0) | Meaning | Error location |\n", - "|:-:|:---|:---|\n", - "| 00 | All parities agree | No error |\n", - "| 01 | q0 and q1 disagree | Error on q0 |\n", - "| 11 | q0-q1 and q1-q2 both disagree | Error on q1 |\n", - "| 10 | q1 and q2 disagree | Error on q2 |\n", - "\n", - "We then apply a corrective X gate to the identified qubit." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "cell-5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "3-Qubit Bit-Flip Code (error on q1):\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "q3\n", - "\n", - "q4\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "M\n", - "M\n", - "\n", - "SWITCH c0\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Measurement results (1000 shots):\n", - " 11 : 1000\n", - "\n", - "Syndrome 11 detected: error correctly identified on qubit 1.\n", - "The Switch block applied X(1) to correct the error.\n" - ] - } - ], - "source": [ - "%%\n", - "// 3-qubit bit-flip code: encode, inject error, syndrome extraction, correction.\n", - "// Qubits: q0-q2 = data qubits, q3-q4 = syndrome ancillae\n", - "// Classical bits: c0-c1 = syndrome results\n", - "\n", - "cBitFlip, err := builder.New(\"bit-flip-code\", 5).WithClbits(2).\n", - "\t// Prepare logical qubit in superposition: |psi> = H|0> = |+>\n", - "\tH(0).\n", - "\t// Encode: |psi> -> alpha|000> + beta|111>\n", - "\tCNOT(0, 1).CNOT(0, 2).\n", - "\t// Inject error: bit flip on qubit 1\n", - "\tX(1).\n", - "\t// Syndrome extraction: check parity of qubit pairs\n", - "\tCNOT(0, 3).CNOT(1, 3). // ancilla q3 = q0 XOR q1\n", - "\tCNOT(1, 4).CNOT(2, 4). // ancilla q4 = q1 XOR q2\n", - "\t// Measure syndromes\n", - "\tMeasure(3, 0). // q3 -> c0 (s0)\n", - "\tMeasure(4, 1). // q4 -> c1 (s1)\n", - "\t// Correction: use Switch on syndrome bits [c0, c1]\n", - "\t// Switch([]int{0, 1}, ...) reads clbits [c0, c1].\n", - "\t// The integer value = c0 + 2*c1:\n", - "\t// c0=1, c1=0 -> value 1 -> error on q0\n", - "\t// c0=0, c1=1 -> value 2 -> error on q2\n", - "\t// c0=1, c1=1 -> value 3 -> error on q1\n", - "\t// c0=0, c1=0 -> value 0 -> no error\n", - "\tSwitch([]int{0, 1}, map[int]func(*builder.Builder){\n", - "\t\t1: func(b *builder.Builder) { b.X(0) }, // c0=1,c1=0 -> fix q0\n", - "\t\t2: func(b *builder.Builder) { b.X(2) }, // c0=0,c1=1 -> fix q2\n", - "\t\t3: func(b *builder.Builder) { b.X(1) }, // c0=1,c1=1 -> fix q1\n", - "\t}, nil).\n", - "\tBuild()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"3-Qubit Bit-Flip Code (error on q1):\")\n", - "gonbui.DisplayHTML(draw.SVG(cBitFlip))\n", - "\n", - "// Run with dynamic circuit support (needed for mid-circuit measurement + Switch)\n", - "simBF := statevector.New(5)\n", - "counts, err := simBF.RunDynamic(cBitFlip, 1000)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"\\nMeasurement results (1000 shots):\")\n", - "for bs, n := range counts {\n", - "\tfmt.Printf(\" %s : %d\\n\", bs, n)\n", - "}\n", - "\n", - "// The syndrome should always be 11 (both parities flagged -> error on q1)\n", - "fmt.Println(\"\\nSyndrome 11 detected: error correctly identified on qubit 1.\")\n", - "fmt.Println(\"The Switch block applied X(1) to correct the error.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-6", - "metadata": {}, - "source": [ - "## Understanding Syndromes\n", - "\n", - "The syndrome is the key insight of quantum error correction. It extracts\n", - "enough information to *locate* the error without revealing anything about\n", - "the *encoded data*.\n", - "\n", - "Here is the complete syndrome truth table for the 3-qubit bit-flip code:\n", - "\n", - "| Error | State after error | q0 XOR q1 (s0) | q1 XOR q2 (s1) | Syndrome | Correction |\n", - "|:---:|:---:|:---:|:---:|:---:|:---:|\n", - "| None | $\\alpha|000\\rangle + \\beta|111\\rangle$ | 0 | 0 | 00 | None |\n", - "| X on q0 | $\\alpha|100\\rangle + \\beta|011\\rangle$ | 1 | 0 | 01 | X(q0) |\n", - "| X on q1 | $\\alpha|010\\rangle + \\beta|101\\rangle$ | 1 | 1 | 11 | X(q1) |\n", - "| X on q2 | $\\alpha|001\\rangle + \\beta|110\\rangle$ | 0 | 1 | 10 | X(q2) |\n", - "\n", - "Notice that each single-qubit error produces a **unique** syndrome.\n", - "This is what allows us to correct the error. If two errors produced the\n", - "same syndrome, we could not distinguish them and correction would fail.\n", - "\n", - "Let's verify this truth table by injecting errors on each qubit." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "cell-7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Error Syndrome Expected Match\n", - "----------------------------------------------\n", - "No error 00 00 PASS\n", - "X on q0 01 01 PASS\n", - "X on q1 11 11 PASS\n", - "X on q2 10 10 PASS\n", - "\n", - "Each error produces a unique syndrome.\n", - "The syndrome reveals the error location without revealing the encoded data.\n" - ] - } - ], - "source": [ - "%%\n", - "// Verify the syndrome truth table by injecting errors on each qubit.\n", - "// We measure only the syndromes (not the data qubits) to confirm detection.\n", - "\n", - "type errorCase struct {\n", - "\tname string\n", - "\terrorQ int // -1 means no error\n", - "\texpected string // expected syndrome [c1 c0]\n", - "}\n", - "\n", - "cases := []errorCase{\n", - "\t{\"No error\", -1, \"00\"},\n", - "\t{\"X on q0\", 0, \"01\"},\n", - "\t{\"X on q1\", 1, \"11\"},\n", - "\t{\"X on q2\", 2, \"10\"},\n", - "}\n", - "\n", - "fmt.Printf(\"%-12s %-10s %-10s %s\\n\", \"Error\", \"Syndrome\", \"Expected\", \"Match\")\n", - "fmt.Println(\"----------------------------------------------\")\n", - "\n", - "for _, tc := range cases {\n", - "\t// Build: encode, inject error, extract syndrome\n", - "\tb := builder.New(\"syndrome-\"+tc.name, 5).WithClbits(2)\n", - "\tb.H(0) // prepare superposition\n", - "\tb.CNOT(0, 1).CNOT(0, 2) // encode\n", - "\n", - "\tif tc.errorQ >= 0 {\n", - "\t\tb.X(tc.errorQ) // inject error\n", - "\t}\n", - "\n", - "\t// Syndrome extraction\n", - "\tb.CNOT(0, 3).CNOT(1, 3) // s0 = q0 XOR q1\n", - "\tb.CNOT(1, 4).CNOT(2, 4) // s1 = q1 XOR q2\n", - "\tb.Measure(3, 0).Measure(4, 1)\n", - "\n", - "\tc, err := b.Build()\n", - "\tif err != nil {\n", - "\t\tpanic(err)\n", - "\t}\n", - "\n", - "\tsim := statevector.New(5)\n", - "\tcounts, err := sim.RunDynamic(c, 100)\n", - "\tif err != nil {\n", - "\t\tpanic(err)\n", - "\t}\n", - "\n", - "\t// Extract the syndrome (the 2 classical bits)\n", - "\tvar syndrome string\n", - "\tfor bs := range counts {\n", - "\t\t// Bitstring is [c1 c0] -- the last 2 characters represent the syndrome bits\n", - "\t\tsyndrome = bs\n", - "\t}\n", - "\n", - "\tmatch := \"PASS\"\n", - "\tif syndrome != tc.expected {\n", - "\t\tmatch = \"FAIL\"\n", - "\t}\n", - "\tfmt.Printf(\"%-12s %-10s %-10s %s\\n\", tc.name, syndrome, tc.expected, match)\n", - "}\n", - "\n", - "fmt.Println(\"\\nEach error produces a unique syndrome.\")\n", - "fmt.Println(\"The syndrome reveals the error location without revealing the encoded data.\")" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "cell-7b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Each error type produces exactly one deterministic syndrome.\n", - "In a real noisy device, syndromes become probabilistic --\n", - "but the most likely syndrome still identifies the most likely error.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "Syndrome Distribution by Error Type\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "20\n", - "\n", - "40\n", - "\n", - "60\n", - "\n", - "80\n", - "\n", - "100\n", - "\n", - "00 (no err)\n", - "\n", - "01 (q0 err)\n", - "\n", - "10 (q2 err)\n", - "\n", - "11 (q1 err)\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Visualize the syndrome results for all error cases.\n", - "// Build a combined histogram showing which syndromes appear for each error.\n", - "\n", - "syndromeLabels := map[string]string{\n", - "\t\"00\": \"No error\",\n", - "\t\"01\": \"X on q0\",\n", - "\t\"11\": \"X on q1\",\n", - "\t\"10\": \"X on q2\",\n", - "}\n", - "\n", - "syndromeCounts := map[string]int{\n", - "\t\"00 (no err)\": 100,\n", - "\t\"01 (q0 err)\": 100,\n", - "\t\"11 (q1 err)\": 100,\n", - "\t\"10 (q2 err)\": 100,\n", - "}\n", - "\n", - "gonbui.DisplayHTML(viz.Histogram(syndromeCounts, viz.WithTitle(\"Syndrome Distribution by Error Type\")))\n", - "\n", - "fmt.Println(\"Each error type produces exactly one deterministic syndrome.\")\n", - "fmt.Println(\"In a real noisy device, syndromes become probabilistic --\")\n", - "fmt.Println(\"but the most likely syndrome still identifies the most likely error.\")\n", - "\n", - "_ = syndromeLabels" - ] - }, - { - "cell_type": "markdown", - "id": "cell-8", - "metadata": {}, - "source": [ - "## The 3-Qubit Phase-Flip Code\n", - "\n", - "The bit-flip code protects against X errors but is helpless against\n", - "**phase-flip errors** (Z errors). A Z error flips the sign of $|1\\rangle$:\n", - "\n", - "$$Z(\\alpha|0\\rangle + \\beta|1\\rangle) = \\alpha|0\\rangle - \\beta|1\\rangle$$\n", - "\n", - "In the computational basis, the codewords $|000\\rangle$ and $|111\\rangle$\n", - "are eigenstates of Z, so Z errors are invisible to the bit-flip code's\n", - "parity checks.\n", - "\n", - "The insight: a phase flip in the Z basis is a bit flip in the **Hadamard\n", - "basis**. If we apply H to every qubit, Z becomes X:\n", - "\n", - "$$H Z H = X$$\n", - "\n", - "So the phase-flip code works exactly like the bit-flip code, but in the\n", - "Hadamard basis:\n", - "\n", - "1. Apply H to all data qubits (switch to Hadamard basis).\n", - "2. Encode using CNOTs (same as bit-flip code).\n", - "3. When a Z error occurs, it acts like an X error in this basis.\n", - "4. Apply H again, extract the syndrome, correct, and apply H to decode.\n", - "\n", - "The logical codewords in the Hadamard basis are:\n", - "\n", - "$$|0\\rangle_L = |{+}{+}{+}\\rangle, \\qquad |1\\rangle_L = |{-}{-}{-}\\rangle$$\n", - "\n", - "where $|{\\pm}\\rangle = (|0\\rangle \\pm |1\\rangle)/\\sqrt{2}$." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "cell-9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "3-Qubit Phase-Flip Code (Z error on q1):\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "q3\n", - "\n", - "q4\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "H\n", - "H\n", - "H\n", - "Z\n", - "H\n", - "H\n", - "H\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "M\n", - "M\n", - "\n", - "SWITCH c0\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Measurement results (1000 shots):\n", - " 11 : 1000\n", - "\n", - "Syndrome 11 detected: phase-flip error on q1 correctly identified.\n", - "The phase-flip code is the bit-flip code conjugated by Hadamard.\n" - ] - } - ], - "source": [ - "%%\n", - "// 3-qubit phase-flip code: protect against Z errors.\n", - "// Strategy: encode in Hadamard basis, then a Z error looks like an X error.\n", - "// Qubits: q0-q2 = data, q3-q4 = syndrome ancillae\n", - "\n", - "cPhaseFlip, err := builder.New(\"phase-flip-code\", 5).WithClbits(2).\n", - "\t// Prepare logical qubit in superposition\n", - "\tH(0).\n", - "\t// Switch to Hadamard basis and encode\n", - "\tCNOT(0, 1).CNOT(0, 2).\n", - "\tH(0).H(1).H(2).\n", - "\t// Inject phase-flip error on qubit 1\n", - "\tZ(1).\n", - "\t// Switch back to computational basis for syndrome extraction\n", - "\tH(0).H(1).H(2).\n", - "\t// Syndrome extraction (same as bit-flip code)\n", - "\tCNOT(0, 3).CNOT(1, 3). // s0 = q0 XOR q1\n", - "\tCNOT(1, 4).CNOT(2, 4). // s1 = q1 XOR q2\n", - "\t// Measure syndromes\n", - "\tMeasure(3, 0).Measure(4, 1).\n", - "\t// Correction (same syndrome table, but correct with Z instead of X)\n", - "\tSwitch([]int{0, 1}, map[int]func(*builder.Builder){\n", - "\t\t1: func(b *builder.Builder) { b.H(0); b.H(1); b.H(2); b.Z(0); b.H(0); b.H(1); b.H(2) },\n", - "\t\t2: func(b *builder.Builder) { b.H(0); b.H(1); b.H(2); b.Z(2); b.H(0); b.H(1); b.H(2) },\n", - "\t\t3: func(b *builder.Builder) { b.H(0); b.H(1); b.H(2); b.Z(1); b.H(0); b.H(1); b.H(2) },\n", - "\t}, nil).\n", - "\tBuild()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"3-Qubit Phase-Flip Code (Z error on q1):\")\n", - "gonbui.DisplayHTML(draw.SVG(cPhaseFlip))\n", - "\n", - "// Run and verify syndrome\n", - "simPF := statevector.New(5)\n", - "countsPF, err := simPF.RunDynamic(cPhaseFlip, 1000)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"\\nMeasurement results (1000 shots):\")\n", - "for bs, n := range countsPF {\n", - "\tfmt.Printf(\" %s : %d\\n\", bs, n)\n", - "}\n", - "\n", - "fmt.Println(\"\\nSyndrome 11 detected: phase-flip error on q1 correctly identified.\")\n", - "fmt.Println(\"The phase-flip code is the bit-flip code conjugated by Hadamard.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-10", - "metadata": {}, - "source": [ - "## The Shor 9-Qubit Code\n", - "\n", - "We now have two codes:\n", - "- The **bit-flip code** corrects X errors but not Z errors.\n", - "- The **phase-flip code** corrects Z errors but not X errors.\n", - "\n", - "Can we combine them to correct *both* types of error? Yes! This is\n", - "exactly what Peter Shor's 9-qubit code does.\n", - "\n", - "### Why this matters: arbitrary errors\n", - "\n", - "Any single-qubit error can be decomposed into a combination of I, X, Y,\n", - "and Z. Since $Y = iXZ$, if we can correct X and Z errors independently,\n", - "we can correct *any* single-qubit error -- including Y errors and\n", - "continuous rotations.\n", - "\n", - "This is the **digitization of errors**: even though quantum errors are\n", - "continuous (a qubit can rotate by any angle), the syndrome measurement\n", - "collapses the error into one of the discrete Pauli errors (I, X, Y, Z),\n", - "which we can then correct.\n", - "\n", - "### Encoding structure\n", - "\n", - "The Shor code uses a two-level encoding:\n", - "\n", - "1. **Outer code (phase-flip protection):** Encode the logical qubit into\n", - " 3 blocks using the phase-flip code structure:\n", - " $$|0\\rangle_L \\to |{+}{+}{+}\\rangle, \\quad |1\\rangle_L \\to |{-}{-}{-}\\rangle$$\n", - "\n", - "2. **Inner code (bit-flip protection):** Each $|{+}\\rangle$ or $|{-}\\rangle$\n", - " is further encoded using the bit-flip code:\n", - " $$|{+}\\rangle \\to \\frac{|000\\rangle + |111\\rangle}{\\sqrt{2}}, \\quad\n", - " |{-}\\rangle \\to \\frac{|000\\rangle - |111\\rangle}{\\sqrt{2}}$$\n", - "\n", - "The full 9-qubit encoding of $|0\\rangle_L$ is:\n", - "$$|0\\rangle_L = \\frac{(|000\\rangle + |111\\rangle)(|000\\rangle + |111\\rangle)(|000\\rangle + |111\\rangle)}{2\\sqrt{2}}$$\n", - "\n", - "Let's build the Shor encoding circuit." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "cell-11", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Shor 9-Qubit Code -- Encoding Circuit:\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "q3\n", - "\n", - "q4\n", - "\n", - "q5\n", - "\n", - "q6\n", - "\n", - "q7\n", - "\n", - "q8\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "H\n", - "H\n", - "H\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Encoded |0>_L statevector (non-zero amplitudes):\n", - " |000000000> : +0.3536\n", - " |000000111> : +0.3536\n", - " |000111000> : +0.3536\n", - " |000111111> : +0.3536\n", - " |111000000> : +0.3536\n", - " |111000111> : +0.3536\n", - " |111111000> : +0.3536\n", - " |111111111> : +0.3536\n", - "\n", - "8 non-zero amplitudes (out of 2^9 = 512 basis states).\n", - "The encoded state is a highly entangled 9-qubit state.\n", - "\n", - "Structure: 3 blocks of (|000> + |111>)/sqrt(2) = 8 terms, each with amplitude 1/(2*sqrt(2)).\n" - ] - } - ], - "source": [ - "%%\n", - "// Shor 9-qubit code: encoding circuit.\n", - "// 9 data qubits (q0-q8) organized in 3 blocks of 3:\n", - "// Block A: q0, q1, q2\n", - "// Block B: q3, q4, q5\n", - "// Block C: q6, q7, q8\n", - "\n", - "cShor, err := builder.New(\"shor-9\", 9).\n", - "\t// Step 1: Phase-flip encoding (outer code)\n", - "\t// Spread the logical qubit across the 3 block leaders\n", - "\tCNOT(0, 3).CNOT(0, 6).\n", - "\t// Apply H to each block leader to switch to +/- basis\n", - "\tH(0).H(3).H(6).\n", - "\t// Step 2: Bit-flip encoding (inner code) within each block\n", - "\t// Block A: q0 -> q0, q1, q2\n", - "\tCNOT(0, 1).CNOT(0, 2).\n", - "\t// Block B: q3 -> q3, q4, q5\n", - "\tCNOT(3, 4).CNOT(3, 5).\n", - "\t// Block C: q6 -> q6, q7, q8\n", - "\tCNOT(6, 7).CNOT(6, 8).\n", - "\tBuild()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Shor 9-Qubit Code -- Encoding Circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(cShor))\n", - "\n", - "// Verify the encoding by checking the statevector\n", - "simShor := statevector.New(9)\n", - "if err := simShor.Evolve(cShor); err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "sv := simShor.StateVector()\n", - "fmt.Println(\"\\nEncoded |0>_L statevector (non-zero amplitudes):\")\n", - "count := 0\n", - "for i, amp := range sv {\n", - "\tp := real(amp)*real(amp) + imag(amp)*imag(amp)\n", - "\tif p > 1e-10 {\n", - "\t\tfmt.Printf(\" |%09b> : %+.4f\\n\", i, real(amp))\n", - "\t\tcount++\n", - "\t}\n", - "}\n", - "fmt.Printf(\"\\n%d non-zero amplitudes (out of 2^9 = 512 basis states).\\n\", count)\n", - "fmt.Println(\"The encoded state is a highly entangled 9-qubit state.\")\n", - "fmt.Println(\"\\nStructure: 3 blocks of (|000> + |111>)/sqrt(2) = 8 terms, each with amplitude 1/(2*sqrt(2)).\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-12", - "metadata": {}, - "source": [ - "### Shor Code Error Correction\n", - "\n", - "Now let's inject different types of errors and verify that the Shor code\n", - "can correct all of them:\n", - "\n", - "- **X error** (bit flip): detected by the inner bit-flip code's\n", - " syndrome measurements within each block.\n", - "- **Z error** (phase flip): detected by comparing the phases between\n", - " blocks (the outer phase-flip code).\n", - "- **Y error**: since $Y = iXZ$, it triggers both the bit-flip and\n", - " phase-flip syndromes simultaneously.\n", - "\n", - "For simplicity, we will demonstrate that the Shor code preserves the\n", - "logical state after each type of error by checking the statevector\n", - "fidelity with the ideal encoded state." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "cell-13", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Error Fid (after error) Fid (corrected) Correctable\n", - "---------------------------------------------------------------------\n", - "No error 1.000000 (no error) N/A\n", - "X on q0 0.000000 1.000000 YES\n", - "X on q4 0.000000 1.000000 YES\n", - "Z on q0 0.000000 1.000000 YES\n", - "Z on q6 0.000000 1.000000 YES\n", - "Y on q3 0.000000 1.000000 YES\n", - "\n", - "The Shor code can correct any single-qubit X, Z, or Y error.\n", - "Y = iXZ, so correcting X and Z independently handles Y as well.\n" - ] - } - ], - "source": [ - "%%\n", - "// Demonstrate Shor code error correction by checking fidelity.\n", - "// We encode |0>_L, inject an error, apply the inverse error (simulating\n", - "// perfect correction), and verify the state is restored.\n", - "//\n", - "// In a real implementation, syndrome measurement would identify the error.\n", - "// Here we show that each error type is correctable in principle.\n", - "\n", - "// Build the Shor encoding circuit for use in this cell\n", - "cShor, _ := builder.New(\"shor-9\", 9).\n", - "\tCNOT(0, 3).CNOT(0, 6).\n", - "\tH(0).H(3).H(6).\n", - "\tCNOT(0, 1).CNOT(0, 2).\n", - "\tCNOT(3, 4).CNOT(3, 5).\n", - "\tCNOT(6, 7).CNOT(6, 8).\n", - "\tBuild()\n", - "\n", - "// First, get the ideal encoded state for fidelity comparison\n", - "simIdeal := statevector.New(9)\n", - "if err := simIdeal.Evolve(cShor); err != nil {\n", - "\tpanic(err)\n", - "}\n", - "idealSV := simIdeal.StateVector()\n", - "\n", - "type shorError struct {\n", - "\tname string\n", - "\tgate gate.Gate\n", - "\tqubit int\n", - "}\n", - "\n", - "errors := []shorError{\n", - "\t{\"No error\", nil, -1},\n", - "\t{\"X on q0\", gate.X, 0},\n", - "\t{\"X on q4\", gate.X, 4},\n", - "\t{\"Z on q0\", gate.Z, 0},\n", - "\t{\"Z on q6\", gate.Z, 6},\n", - "\t{\"Y on q3\", gate.Y, 3},\n", - "}\n", - "\n", - "fmt.Printf(\"%-12s %-18s %-18s %s\\n\", \"Error\", \"Fid (after error)\", \"Fid (corrected)\", \"Correctable\")\n", - "fmt.Println(\"---------------------------------------------------------------------\")\n", - "\n", - "for _, e := range errors {\n", - "\t// Build: encode + inject error\n", - "\tb := builder.New(\"shor-\"+e.name, 9)\n", - "\tb.CNOT(0, 3).CNOT(0, 6)\n", - "\tb.H(0).H(3).H(6)\n", - "\tb.CNOT(0, 1).CNOT(0, 2)\n", - "\tb.CNOT(3, 4).CNOT(3, 5)\n", - "\tb.CNOT(6, 7).CNOT(6, 8)\n", - "\n", - "\tif e.gate != nil {\n", - "\t\tb.Apply(e.gate, e.qubit)\n", - "\t}\n", - "\n", - "\tcErr, err := b.Build()\n", - "\tif err != nil {\n", - "\t\tpanic(err)\n", - "\t}\n", - "\n", - "\t// Measure fidelity after error (before correction)\n", - "\tsimErr := statevector.New(9)\n", - "\tif err := simErr.Evolve(cErr); err != nil {\n", - "\t\tpanic(err)\n", - "\t}\n", - "\terrSV := simErr.StateVector()\n", - "\n", - "\t// Fidelity = ||^2\n", - "\tvar dot complex128\n", - "\tfor i := range idealSV {\n", - "\t\tdot += complex(real(idealSV[i]), -imag(idealSV[i])) * errSV[i]\n", - "\t}\n", - "\tfidErr := real(dot)*real(dot) + imag(dot)*imag(dot)\n", - "\n", - "\t// Now apply correction (the inverse of the error)\n", - "\tif e.gate != nil {\n", - "\t\tb2 := builder.New(\"shor-corr-\"+e.name, 9)\n", - "\t\tb2.CNOT(0, 3).CNOT(0, 6)\n", - "\t\tb2.H(0).H(3).H(6)\n", - "\t\tb2.CNOT(0, 1).CNOT(0, 2)\n", - "\t\tb2.CNOT(3, 4).CNOT(3, 5)\n", - "\t\tb2.CNOT(6, 7).CNOT(6, 8)\n", - "\t\tb2.Apply(e.gate, e.qubit) // inject error\n", - "\t\tb2.Apply(e.gate, e.qubit) // apply same gate again to correct (X^2=I, Z^2=I, Y^2=-I~I)\n", - "\n", - "\t\tcCorr, _ := b2.Build()\n", - "\t\tsimCorr := statevector.New(9)\n", - "\t\tsimCorr.Evolve(cCorr)\n", - "\t\tcorrSV := simCorr.StateVector()\n", - "\n", - "\t\tvar dot2 complex128\n", - "\t\tfor i := range idealSV {\n", - "\t\t\tdot2 += complex(real(idealSV[i]), -imag(idealSV[i])) * corrSV[i]\n", - "\t\t}\n", - "\t\tfidCorr := real(dot2)*real(dot2) + imag(dot2)*imag(dot2)\n", - "\n", - "\t\tcorrectable := \"YES\"\n", - "\t\tif fidCorr < 0.99 {\n", - "\t\t\tcorrectable = \"NO\"\n", - "\t\t}\n", - "\t\tfmt.Printf(\"%-12s %-18.6f %-18.6f %s\\n\", e.name, fidErr, fidCorr, correctable)\n", - "\t} else {\n", - "\t\tfmt.Printf(\"%-12s %-18.6f %-18s %s\\n\", e.name, fidErr, \"(no error)\", \"N/A\")\n", - "\t}\n", - "}\n", - "\n", - "fmt.Println(\"\\nThe Shor code can correct any single-qubit X, Z, or Y error.\")\n", - "fmt.Println(\"Y = iXZ, so correcting X and Z independently handles Y as well.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-14", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** What syndrome does a Y error produce in the 3-qubit bit-flip code? Will the bit-flip code be able to fully correct the error?\n", - "\n", - "**Pause and predict** before running the next cell.\n", - "\n", - "*Hint: The Y gate is $Y = iXZ$. Think about which component the bit-flip code's parity checks can detect, and what happens to the other component.*" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "cell-15", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Y error on q1 -- syndrome measurements (1000 shots):\n", - " 11 : 1000\n", - "\n", - "Prediction confirmed: Y error produces syndrome 11 (same as X on q1).\n", - "The bit-flip code detects the X component but misses the Z component.\n", - "Full Y correction requires the Shor code (or a more general code).\n" - ] - } - ], - "source": [ - "%%\n", - "// Verify: Y error on q1 in the bit-flip code.\n", - "// The syndrome should match X on q1 (syndrome 11),\n", - "// but the residual Z error makes correction incomplete.\n", - "\n", - "cYError, err := builder.New(\"y-error-test\", 5).WithClbits(2).\n", - "\tH(0).\n", - "\tCNOT(0, 1).CNOT(0, 2).\n", - "\tY(1). // Y = iXZ -- has both bit-flip and phase-flip components\n", - "\tCNOT(0, 3).CNOT(1, 3).\n", - "\tCNOT(1, 4).CNOT(2, 4).\n", - "\tMeasure(3, 0).Measure(4, 1).\n", - "\tBuild()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "simY := statevector.New(5)\n", - "countsY, err := simY.RunDynamic(cYError, 1000)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Y error on q1 -- syndrome measurements (1000 shots):\")\n", - "for bs, n := range countsY {\n", - "\tfmt.Printf(\" %s : %d\\n\", bs, n)\n", - "}\n", - "\n", - "fmt.Println(\"\\nPrediction confirmed: Y error produces syndrome 11 (same as X on q1).\")\n", - "fmt.Println(\"The bit-flip code detects the X component but misses the Z component.\")\n", - "fmt.Println(\"Full Y correction requires the Shor code (or a more general code).\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-16", - "metadata": {}, - "source": [ - "## Stabilizer Formalism Preview\n", - "\n", - "The stabilizer formalism provides a powerful mathematical framework for\n", - "quantum error correction. Instead of tracking the full $2^n$-dimensional\n", - "statevector, we describe the code space using a set of **stabilizer\n", - "operators** -- Pauli operators that leave the code states unchanged.\n", - "\n", - "For the 3-qubit bit-flip code, the stabilizers are:\n", - "\n", - "$$S_1 = Z_0 Z_1 I_2, \\qquad S_2 = I_0 Z_1 Z_2$$\n", - "\n", - "These operators have eigenvalue $+1$ on the valid codewords:\n", - "- $S_1 |000\\rangle = +|000\\rangle$ and $S_1 |111\\rangle = +|111\\rangle$\n", - "- $S_2 |000\\rangle = +|000\\rangle$ and $S_2 |111\\rangle = +|111\\rangle$\n", - "\n", - "When an X error occurs on qubit $j$, it anticommutes with some stabilizers,\n", - "flipping their eigenvalue to $-1$. This eigenvalue change *is* the syndrome.\n", - "\n", - "The Gottesman-Knill theorem tells us that circuits composed entirely of\n", - "Clifford gates (H, S, CNOT, Pauli gates) acting on stabilizer states can\n", - "be efficiently simulated in $O(n^2)$ time per gate using the **stabilizer\n", - "tableau** representation. This is exactly what Goqu's `clifford.Sim`\n", - "implements (see notebook 16).\n", - "\n", - "Let's use the Clifford simulator to efficiently simulate a large\n", - "repetition code -- something a statevector simulator could not handle." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "cell-17", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "n= 3 qubits: 2 distinct outcomes, majority vote correct: 51/100\n", - "n= 7 qubits: 2 distinct outcomes, majority vote correct: 100/100\n", - "n= 15 qubits: 2 distinct outcomes, majority vote correct: 100/100\n", - "n= 51 qubits: 2 distinct outcomes, majority vote correct: 100/100\n", - "n=101 qubits: 2 distinct outcomes, majority vote correct: 100/100\n", - "\n", - "The Clifford simulator handles 101 qubits in milliseconds.\n", - "A statevector simulator would need 2^101 amplitudes -- impossible!\n", - "\n", - "This efficiency comes from the Gottesman-Knill theorem:\n", - "Clifford circuits (H, S, CNOT, Pauli) on stabilizer states\n", - "can be classically simulated in O(n^2) time per gate.\n" - ] - } - ], - "source": [ - "%%\n", - "// Use the Clifford simulator to simulate a large repetition code.\n", - "// The bit-flip code with n data qubits encodes 1 logical qubit using\n", - "// n-1 CNOT gates. With the Clifford simulator, we can scale to\n", - "// hundreds of qubits efficiently.\n", - "\n", - "for _, nData := range []int{3, 7, 15, 51, 101} {\n", - "\t// Build an n-qubit repetition code: encode, inject error, measure\n", - "\tb := builder.New(fmt.Sprintf(\"rep-%d\", nData), nData)\n", - "\n", - "\t// Prepare logical |+> state\n", - "\tb.H(0)\n", - "\n", - "\t// Encode: CNOT from q0 to all other qubits\n", - "\tfor i := 1; i < nData; i++ {\n", - "\t\tb.CNOT(0, i)\n", - "\t}\n", - "\n", - "\t// Inject a single X error in the middle of the code block\n", - "\terrorQ := nData / 2\n", - "\tb.X(errorQ)\n", - "\n", - "\t// Measure all qubits\n", - "\tb.MeasureAll()\n", - "\n", - "\tc, err := b.Build()\n", - "\tif err != nil {\n", - "\t\tpanic(err)\n", - "\t}\n", - "\n", - "\t// Run with Clifford simulator\n", - "\tsim := clifford.New(nData)\n", - "\tcounts, err := sim.Run(c, 100)\n", - "\tif err != nil {\n", - "\t\tpanic(err)\n", - "\t}\n", - "\n", - "\t// Check: with one error, majority vote should still recover the correct logical value\n", - "\tmajorityCorrect := 0\n", - "\ttotalShots := 0\n", - "\tfor bs, n := range counts {\n", - "\t\ttotalShots += n\n", - "\t\t// Count ones in the bitstring\n", - "\t\tones := 0\n", - "\t\tfor _, ch := range bs {\n", - "\t\t\tif ch == '1' {\n", - "\t\t\t\tones++\n", - "\t\t\t}\n", - "\t\t}\n", - "\t\t// Majority vote: if more than half are 1, logical qubit = 1\n", - "\t\t// With one X error on a |+> encoded state, all qubits should agree\n", - "\t\t// except the errored one. Majority vote still recovers the correct value.\n", - "\t\tif ones > nData/2 || ones < nData/2 {\n", - "\t\t\tmajorityCorrect += n // majority is clear\n", - "\t\t}\n", - "\t}\n", - "\n", - "\tfmt.Printf(\"n=%3d qubits: %d distinct outcomes, majority vote correct: %d/%d\\n\",\n", - "\t\tnData, len(counts), majorityCorrect, totalShots)\n", - "}\n", - "\n", - "fmt.Println(\"\\nThe Clifford simulator handles 101 qubits in milliseconds.\")\n", - "fmt.Println(\"A statevector simulator would need 2^101 amplitudes -- impossible!\")\n", - "fmt.Println(\"\\nThis efficiency comes from the Gottesman-Knill theorem:\")\n", - "fmt.Println(\"Clifford circuits (H, S, CNOT, Pauli) on stabilizer states\")\n", - "fmt.Println(\"can be classically simulated in O(n^2) time per gate.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-17b", - "metadata": {}, - "source": [ - "## Noise and the Bit-Flip Code\n", - "\n", - "So far we have injected errors manually (applying X gates). In reality,\n", - "errors occur probabilistically due to noise. Let's use the density matrix\n", - "simulator with bit-flip noise to see how the 3-qubit code performs under\n", - "realistic conditions.\n", - "\n", - "We will encode a logical $|0\\rangle$, apply bit-flip noise to the CNOT\n", - "gates, and check whether majority vote on the measurement outcomes still\n", - "recovers the correct logical value." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "cell-17c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " p_phys Unencoded p_L Encoded p_L Code helps?\n", - "------------------------------------------------------\n", - " 0.01 0.0100 0.0109 NO\n", - " 0.05 0.0500 0.0488 YES\n", - " 0.10 0.1000 0.0882 YES\n", - " 0.20 0.2000 0.1547 YES\n", - "\n", - "For small physical error rates, the encoded logical error rate\n", - "is lower than the unencoded rate. This is the threshold theorem in action.\n", - "The crossover point (where encoding stops helping) is around p = 0.5.\n" - ] - } - ], - "source": [ - "%%\n", - "// Noisy bit-flip code: use density matrix simulator with bit-flip noise.\n", - "// Compare unencoded (1 qubit) vs encoded (3 qubits) logical error rates.\n", - "\n", - "fmt.Printf(\"%8s %14s %14s %s\\n\", \"p_phys\", \"Unencoded p_L\", \"Encoded p_L\", \"Code helps?\")\n", - "fmt.Println(\"------------------------------------------------------\")\n", - "\n", - "for _, p := range []float64{0.01, 0.05, 0.10, 0.20} {\n", - "\t// Encoded: 3-qubit bit-flip code\n", - "\tcEnc, _ := builder.New(\"enc\", 3).\n", - "\t\tCNOT(0, 1).CNOT(0, 2).\n", - "\t\tMeasureAll().\n", - "\t\tBuild()\n", - "\n", - "\tnm := noise.New()\n", - "\tnm.AddGateError(\"CNOT\", noise.BitFlip(p))\n", - "\n", - "\tsim := densitymatrix.New(3).WithNoise(nm)\n", - "\tcounts, err := sim.Run(cEnc, 10000)\n", - "\tif err != nil {\n", - "\t\tpanic(err)\n", - "\t}\n", - "\n", - "\t// Majority vote: count logical errors\n", - "\tlogicalErrors := 0\n", - "\ttotalShots := 0\n", - "\tfor bs, n := range counts {\n", - "\t\ttotalShots += n\n", - "\t\tones := 0\n", - "\t\tfor _, ch := range bs {\n", - "\t\t\tif ch == '1' { ones++ }\n", - "\t\t}\n", - "\t\tif ones > 1 {\n", - "\t\t\tlogicalErrors += n\n", - "\t\t}\n", - "\t}\n", - "\n", - "\tpLEncoded := float64(logicalErrors) / float64(totalShots)\n", - "\thelps := \"YES\"\n", - "\tif pLEncoded >= p {\n", - "\t\thelps = \"NO\"\n", - "\t}\n", - "\n", - "\tfmt.Printf(\"%8.2f %14.4f %14.4f %s\\n\", p, p, pLEncoded, helps)\n", - "}\n", - "\n", - "fmt.Println(\"\\nFor small physical error rates, the encoded logical error rate\")\n", - "fmt.Println(\"is lower than the unencoded rate. This is the threshold theorem in action.\")\n", - "fmt.Println(\"The crossover point (where encoding stops helping) is around p = 0.5.\")" - ] - }, - { - "cell_type": "markdown", - "id": "dhm68fry9a4", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. Why does the no-cloning theorem prevent us from using classical majority-vote error correction on qubits?\n", - "2. How does syndrome measurement detect an error without revealing the encoded quantum state?\n", - "3. Why does the Shor 9-qubit code need both bit-flip and phase-flip protection to correct an arbitrary single-qubit error?" - ] - }, - { - "cell_type": "markdown", - "id": "cell-18", - "metadata": {}, - "source": [ - "## Exercises\n", - "\n", - "### Exercise 1: Bit-Flip Code with Error on q2\n", - "\n", - "Modify the 3-qubit bit-flip code to inject the error on **qubit 2**\n", - "instead of qubit 1. Verify that the syndrome changes from 11 to **10**\n", - "and that the Switch correction block properly corrects the error.\n", - "\n", - "*Hint:* The syndrome table says an error on q2 produces syndrome 10\n", - "(q0-q1 parity is fine, q1-q2 parity is broken)." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "cell-19", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the error qubit!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: 3-qubit bit-flip code with error on q2\n", - "//\n", - "// TODO: Build the bit-flip code circuit with error on q2 instead of q1.\n", - "// The syndrome should be 10 (only the q1-q2 parity check fires).\n", - "//\n", - "// Skeleton:\n", - "\n", - "// cEx1, err := builder.New(\"bit-flip-ex1\", 5).WithClbits(2).\n", - "// \tH(0).\n", - "// \tCNOT(0, 1).CNOT(0, 2).\n", - "// \tX(???). // TODO: inject error on q2\n", - "// \tCNOT(0, 3).CNOT(1, 3).\n", - "// \tCNOT(1, 4).CNOT(2, 4).\n", - "// \tMeasure(3, 0).Measure(4, 1).\n", - "// \tSwitch([]int{0, 1}, map[int]func(*builder.Builder){\n", - "// \t\t1: func(b *builder.Builder) { b.X(0) },\n", - "// \t\t2: func(b *builder.Builder) { b.X(2) },\n", - "// \t\t3: func(b *builder.Builder) { b.X(1) },\n", - "// \t}, nil).\n", - "// \tBuild()\n", - "// if err != nil {\n", - "// \tpanic(err)\n", - "// }\n", - "//\n", - "// fmt.Println(\"Bit-flip code with error on q2:\")\n", - "// fmt.Println(draw.String(cEx1))\n", - "//\n", - "// simEx1 := statevector.New(5)\n", - "// countsEx1, err := simEx1.RunDynamic(cEx1, 1000)\n", - "// if err != nil {\n", - "// \tpanic(err)\n", - "// }\n", - "//\n", - "// fmt.Println(\"\\nSyndrome results (1000 shots):\")\n", - "// for bs, n := range countsEx1 {\n", - "// \tfmt.Printf(\" %s : %d\\n\", bs, n)\n", - "// }\n", - "//\n", - "// // TODO: Verify syndrome is 10\n", - "// // TODO: Verify correction was applied successfully\n", - "\n", - "fmt.Println(\"Uncomment the code above and fill in the error qubit!\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-20", - "metadata": {}, - "source": [ - "### Exercise 2: Logical Error Rate vs Physical Error Rate\n", - "\n", - "The whole point of error correction is to make the **logical error rate**\n", - "lower than the **physical error rate**. For the 3-qubit bit-flip code,\n", - "the logical qubit fails only if 2 or more of the 3 physical qubits\n", - "experience an error. If each physical qubit has an independent bit-flip\n", - "probability $p$, the logical error rate is:\n", - "\n", - "$$p_L = 3p^2(1-p) + p^3 = 3p^2 - 2p^3$$\n", - "\n", - "For $p < 0.5$, $p_L < p$ -- the code actually helps! This is the\n", - "**threshold theorem** in action: below a critical error rate, adding\n", - "more error correction makes things better, not worse.\n", - "\n", - "Use the density matrix simulator with bit-flip noise to measure the\n", - "logical error rate at several physical error rates and compare with\n", - "the theoretical prediction." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "cell-21", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and fill in the noise parameter!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: Logical error rate vs physical error rate\n", - "//\n", - "// TODO: For several values of physical error rate p (0.01 to 0.4),\n", - "// run the 3-qubit bit-flip code with BitFlip(p) noise on each gate,\n", - "// measure the outcome, and compute the logical error rate.\n", - "// Compare with the theoretical formula: p_L = 3p^2 - 2p^3.\n", - "//\n", - "// Skeleton:\n", - "\n", - "// fmt.Printf(\"%8s %12s %12s\\n\", \"p_phys\", \"p_L (sim)\", \"p_L (theory)\")\n", - "// fmt.Println(\"--------------------------------------\")\n", - "//\n", - "// for _, p := range []float64{0.01, 0.05, 0.10, 0.15, 0.20, 0.30, 0.40} {\n", - "// // Build the bit-flip encoding + measurement circuit\n", - "// // (encode, then measure all 3 data qubits)\n", - "// cEnc, _ := builder.New(\"enc\", 3).\n", - "// CNOT(0, 1).CNOT(0, 2).\n", - "// MeasureAll().\n", - "// Build()\n", - "//\n", - "// // Create a noise model with BitFlip(p) on all gates\n", - "// nm := noise.New()\n", - "// nm.AddGateError(\"CNOT\", noise.BitFlip(???))\n", - "//\n", - "// // Run noisy simulation with many shots\n", - "// sim := densitymatrix.New(3).WithNoise(nm)\n", - "// counts, err := sim.Run(cEnc, 10000)\n", - "// if err != nil {\n", - "// panic(err)\n", - "// }\n", - "//\n", - "// // Count logical errors: majority vote on the 3 bits\n", - "// // If majority = 0, logical qubit = |0>; if majority = 1, logical error\n", - "// // (since we started with |0>)\n", - "// logicalErrors := 0\n", - "// totalShots := 0\n", - "// for bs, n := range counts {\n", - "// totalShots += n\n", - "// ones := 0\n", - "// for _, ch := range bs {\n", - "// if ch == '1' { ones++ }\n", - "// }\n", - "// if ones > 1 { // majority = 1 -> logical error\n", - "// logicalErrors += n\n", - "// }\n", - "// }\n", - "//\n", - "// pLSim := float64(logicalErrors) / float64(totalShots)\n", - "// pLTheory := 3*p*p - 2*p*p*p\n", - "//\n", - "// fmt.Printf(\"%8.2f %12.4f %12.4f\\n\", p, pLSim, pLTheory)\n", - "// }\n", - "//\n", - "// fmt.Println(\"\\nBelow p ~ 0.5, the logical error rate is lower than the physical rate.\")\n", - "// fmt.Println(\"This demonstrates the threshold theorem for the repetition code.\")\n", - "\n", - "fmt.Println(\"Uncomment the code above and fill in the noise parameter!\")" - ] - }, - { - "cell_type": "markdown", - "id": "60ci6ygpw2h", - "metadata": {}, - "source": [ - "## Surface Codes and the Road to Fault Tolerance\n", - "\n", - "The repetition and Shor codes we explored in this notebook are\n", - "pedagogically important but not practical for real hardware. The leading\n", - "candidate for fault-tolerant quantum computing is the **surface code**:\n", - "\n", - "- The surface code arranges qubits on a 2D grid with nearest-neighbor\n", - " connectivity -- matching the layout of most superconducting quantum\n", - " processors.\n", - "\n", - "- It has a relatively high **error threshold** (~1%), meaning that if\n", - " physical gate error rates are below this threshold, adding more\n", - " qubits actually reduces the logical error rate exponentially.\n", - "\n", - "- In December 2024, **Google's Willow chip** demonstrated\n", - " below-threshold surface code operation for the first time: increasing\n", - " the code distance (adding more physical qubits) reduced the logical\n", - " error rate, rather than increasing it. This is a landmark milestone\n", - " toward scalable quantum error correction.\n", - "\n", - "- **IBM** is pursuing quantum Low-Density Parity-Check (qLDPC) codes,\n", - " which offer better encoding rates than surface codes (more logical\n", - " qubits per physical qubit), though they require longer-range\n", - " connectivity.\n", - "\n", - "The threshold theorem guarantees that fault-tolerant quantum computation\n", - "is possible in principle. The experimental demonstrations of 2024-2025\n", - "are making it a reality. The path from here to practical fault-tolerant\n", - "quantum computing involves scaling to thousands of physical qubits per\n", - "logical qubit -- a formidable engineering challenge, but one with a\n", - "clear theoretical roadmap." - ] - }, - { - "cell_type": "markdown", - "id": "cell-22", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "\n", - "1. **Quantum error correction is fundamentally harder than classical EC.**\n", - " The no-cloning theorem forbids copying unknown quantum states, and\n", - " measurement destroys superposition. QEC must extract error information\n", - " (syndromes) without learning the encoded data.\n", - "\n", - "2. **The 3-qubit bit-flip code** encodes $|0\\rangle_L = |000\\rangle$,\n", - " $|1\\rangle_L = |111\\rangle$. Parity checks on pairs of qubits reveal\n", - " which qubit was flipped without measuring the logical qubit.\n", - "\n", - "3. **Syndrome measurement** is the central mechanism of QEC. Ancilla\n", - " qubits are entangled with data qubits via CNOTs, then measured.\n", - " The measurement outcome (syndrome) identifies the error without\n", - " collapsing the encoded state.\n", - "\n", - "4. **The 3-qubit phase-flip code** is the bit-flip code in the Hadamard\n", - " basis. It corrects Z errors by converting them to X errors via H gates.\n", - "\n", - "5. **The Shor 9-qubit code** combines bit-flip and phase-flip protection.\n", - " It can correct *any* single-qubit error (X, Y, or Z) because Y = iXZ,\n", - " and the code handles X and Z independently.\n", - "\n", - "6. **Error digitization:** even though quantum errors are continuous\n", - " (arbitrary rotations), syndrome measurement projects them onto\n", - " discrete Pauli errors (I, X, Y, Z) that can be corrected.\n", - "\n", - "7. **The stabilizer formalism** provides the mathematical backbone for\n", - " QEC codes. The Gottesman-Knill theorem enables efficient classical\n", - " simulation of Clifford circuits via stabilizer tableaux -- see\n", - " the `clifford.Sim` in notebook 16.\n", - "\n", - "8. **The threshold theorem** guarantees that below a critical physical\n", - " error rate, adding more error correction reduces the logical error\n", - " rate exponentially. This is the theoretical foundation for\n", - " fault-tolerant quantum computing." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/12-transpilation.ipynb b/notebooks/12-transpilation.ipynb deleted file mode 100644 index bad5a26..0000000 --- a/notebooks/12-transpilation.ipynb +++ /dev/null @@ -1,1439 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a1b2c3d4", - "metadata": {}, - "source": [ - "# Notebook 12 -- Transpilation and Hardware Constraints\n", - "\n", - "**Prerequisites:** Notebooks 01-06. Familiarity with multi-qubit gates.\n", - "\n", - "Real quantum hardware is not a universal instruction set machine. Each\n", - "processor supports only a small set of **basis gates** (e.g., CX + RZ + SX\n", - "on IBM, or CZ + RZ + RX on Google) and has limited **qubit connectivity**\n", - "(most qubits can only interact with their nearest neighbors).\n", - "\n", - "**Transpilation** is the process of converting an abstract quantum circuit\n", - "into one that respects these hardware constraints:\n", - "\n", - "1. **Gate decomposition** -- replace high-level gates (Toffoli, SWAP, etc.)\n", - " with sequences of basis gates the hardware can execute.\n", - "2. **Qubit routing** -- insert SWAP gates so that two-qubit gates only act\n", - " on physically connected qubits.\n", - "3. **Optimization** -- cancel redundant gates, merge rotations, and\n", - " parallelize operations to reduce depth and error.\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Describe** hardware constraints (basis gates, connectivity) for different quantum processors.\n", - "2. **Implement** transpilation to specific hardware targets at different optimization levels.\n", - "3. **Explain** Euler and KAK gate decomposition.\n", - "4. **Verify** that transpilation preserves circuit functionality.\n", - "\n", - "In this notebook we will:\n", - "\n", - "- Explore hardware targets and their basis gates.\n", - "- Transpile circuits with `pipeline.Run` at different optimization levels.\n", - "- Decompose single-qubit gates (Euler) and two-qubit gates (KAK).\n", - "- Route qubits with the SABRE algorithm.\n", - "- Apply individual transpilation passes.\n", - "- Verify that transpilation preserves circuit functionality." - ] - }, - { - "cell_type": "markdown", - "id": "72p2n2bgnet", - "metadata": {}, - "source": [ - "### Why this matters\n", - "\n", - "You have been building circuits using gates like Toffoli, controlled-RY, and multi-controlled X. **No quantum processor can execute these gates directly.** Real hardware supports only a handful of native operations (e.g., CNOT + single-qubit rotations on IBM devices). Transpilation is the bridge between your high-level circuit and what the hardware can actually run -- and how well it is done directly affects the quality of your results." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "b2c3d4e5", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"context\"\n", - "\t\"fmt\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/circuit/gate\"\n", - "\t\"github.com/splch/goqu/circuit/ir\"\n", - "\t\"github.com/splch/goqu/transpile/decompose\"\n", - "\t\"github.com/splch/goqu/transpile/pass\"\n", - "\t\"github.com/splch/goqu/transpile/pipeline\"\n", - "\t\"github.com/splch/goqu/transpile/routing\"\n", - "\t\"github.com/splch/goqu/transpile/target\"\n", - "\t\"github.com/splch/goqu/transpile/verify\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "c3d4e5f6", - "metadata": {}, - "source": [ - "## Hardware Targets\n", - "\n", - "Each hardware target in Goqu is described by a `target.Target` struct with:\n", - "\n", - "- **Name** -- the hardware identifier.\n", - "- **NumQubits** -- number of physical qubits.\n", - "- **BasisGates** -- the native gate set the processor can execute.\n", - "- **Connectivity** -- which qubit pairs are physically connected\n", - " (`nil` means all-to-all, as in trapped-ion machines).\n", - "\n", - "Different hardware families have fundamentally different native gates:\n", - "\n", - "| Family | 2Q Gate | 1Q Gates | Connectivity |\n", - "|--------|---------|----------|-------------|\n", - "| IBM (superconducting) | CX | RZ, SX, X | Heavy-hex lattice |\n", - "| Google (superconducting) | CZ | RZ, RX | 2D grid |\n", - "| IonQ (trapped ion) | MS | GPI, GPI2 | All-to-all |\n", - "| Quantinuum (trapped ion) | RZZ | RZ, RY | All-to-all |\n", - "| Rigetti (superconducting) | CZ | RX, RZ | Square-octagon lattice |\n", - "\n", - "Let's inspect the available targets." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "d4e5f6a7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Available Hardware Targets\n", - "=========================\n", - "ibm.brisbane basis=[CX RZ SX X I ] qubits=127 connectivity=144 edges\n", - "IBM Eagle basis=[CX ID RZ SX X ] qubits=127 connectivity=144 edges\n", - "Google Sycamore basis=[CZ RZ RX ] qubits=53 connectivity=all-to-all\n", - "IonQ Forte basis=[GPI GPI2 MS ] qubits=36 connectivity=all-to-all\n", - "Quantinuum H1 basis=[RZZ RZ RY ] qubits=20 connectivity=all-to-all\n", - "Rigetti Ankaa-3 basis=[CZ RX RZ ] qubits=84 connectivity=all-to-all\n", - "\n", - "Note: trapped-ion machines (IonQ, Quantinuum) have all-to-all connectivity,\n", - "meaning any two qubits can interact directly without SWAP routing.\n" - ] - } - ], - "source": [ - "%%\n", - "targets := []target.Target{\n", - "\ttarget.IBMBrisbane,\n", - "\ttarget.IBMEagle,\n", - "\ttarget.GoogleSycamore,\n", - "\ttarget.IonQForte,\n", - "\ttarget.QuantinuumH1,\n", - "\ttarget.RigettiAnkaa,\n", - "}\n", - "\n", - "fmt.Println(\"Available Hardware Targets\")\n", - "fmt.Println(\"=========================\")\n", - "for _, t := range targets {\n", - "\tconnectivity := \"all-to-all\"\n", - "\tif t.Connectivity != nil {\n", - "\t\tconnectivity = fmt.Sprintf(\"%d edges\", len(t.Connectivity))\n", - "\t}\n", - "\tfmt.Printf(\"%-20s basis=%-28v qubits=%d connectivity=%s\\n\",\n", - "\t\tt.Name, t.BasisGates, t.NumQubits, connectivity)\n", - "}\n", - "\n", - "fmt.Println(\"\\nNote: trapped-ion machines (IonQ, Quantinuum) have all-to-all connectivity,\")\n", - "fmt.Println(\"meaning any two qubits can interact directly without SWAP routing.\")" - ] - }, - { - "cell_type": "markdown", - "id": "e5f6a7b8", - "metadata": {}, - "source": [ - "## Transpilation with `pipeline.Run`\n", - "\n", - "The `pipeline.Run` function is the main entry point for transpilation. It\n", - "takes an abstract circuit and a hardware target, then applies a sequence of\n", - "passes (decomposition, routing, optimization) to produce a hardware-compatible\n", - "circuit.\n", - "\n", - "```go\n", - "out, err := pipeline.Run(ctx, circuit, target, level)\n", - "```\n", - "\n", - "Let's transpile a Toffoli (CCX) gate -- a 3-qubit gate that is not in any\n", - "hardware's native basis set -- to the IBM Eagle target." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "f6a7b8c9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Before transpilation:\n", - "Stats: depth=1, gates=1, 2Q=1\n", - "\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "After transpilation (IBM Eagle, LevelFull):\n", - "Stats: depth=29, gates=49, 2Q=9\n", - "\n", - "The single CCX gate has been decomposed into CX + single-qubit rotations.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "q3\n", - "\n", - "q4\n", - "\n", - "q5\n", - "\n", - "q6\n", - "\n", - "q7\n", - "\n", - "q8\n", - "\n", - "q9\n", - "\n", - "q10\n", - "\n", - "q11\n", - "\n", - "q12\n", - "\n", - "q13\n", - "\n", - "q14\n", - "\n", - "q15\n", - "\n", - "q16\n", - "\n", - "q17\n", - "\n", - "q18\n", - "\n", - "q19\n", - "\n", - "q20\n", - "\n", - "q21\n", - "\n", - "q22\n", - "\n", - "q23\n", - "\n", - "q24\n", - "\n", - "q25\n", - "\n", - "q26\n", - "\n", - "q27\n", - "\n", - "q28\n", - "\n", - "q29\n", - "\n", - "q30\n", - "\n", - "q31\n", - "\n", - "q32\n", - "\n", - "q33\n", - "\n", - "q34\n", - "\n", - "q35\n", - "\n", - "q36\n", - "\n", - "q37\n", - "\n", - "q38\n", - "\n", - "q39\n", - "\n", - "q40\n", - "\n", - "q41\n", - "\n", - "q42\n", - "\n", - "q43\n", - "\n", - "q44\n", - "\n", - "q45\n", - "\n", - "q46\n", - "\n", - "q47\n", - "\n", - "q48\n", - "\n", - "q49\n", - "\n", - "q50\n", - "\n", - "q51\n", - "\n", - "q52\n", - "\n", - "q53\n", - "\n", - "q54\n", - "\n", - "q55\n", - "\n", - "q56\n", - "\n", - "q57\n", - "\n", - "q58\n", - "\n", - "q59\n", - "\n", - "q60\n", - "\n", - "q61\n", - "\n", - "q62\n", - "\n", - "q63\n", - "\n", - "q64\n", - "\n", - "q65\n", - "\n", - "q66\n", - "\n", - "q67\n", - "\n", - "q68\n", - "\n", - "q69\n", - "\n", - "q70\n", - "\n", - "q71\n", - "\n", - "q72\n", - "\n", - "q73\n", - "\n", - "q74\n", - "\n", - "q75\n", - "\n", - "q76\n", - "\n", - "q77\n", - "\n", - "q78\n", - "\n", - "q79\n", - "\n", - "q80\n", - "\n", - "q81\n", - "\n", - "q82\n", - "\n", - "q83\n", - "\n", - "q84\n", - "\n", - "q85\n", - "\n", - "q86\n", - "\n", - "q87\n", - "\n", - "q88\n", - "\n", - "q89\n", - "\n", - "q90\n", - "\n", - "q91\n", - "\n", - "q92\n", - "\n", - "q93\n", - "\n", - "q94\n", - "\n", - "q95\n", - "\n", - "q96\n", - "\n", - "q97\n", - "\n", - "q98\n", - "\n", - "q99\n", - "\n", - "q100\n", - "\n", - "q101\n", - "\n", - "q102\n", - "\n", - "q103\n", - "\n", - "q104\n", - "\n", - "q105\n", - "\n", - "q106\n", - "\n", - "q107\n", - "\n", - "q108\n", - "\n", - "q109\n", - "\n", - "q110\n", - "\n", - "q111\n", - "\n", - "q112\n", - "\n", - "q113\n", - "\n", - "q114\n", - "\n", - "q115\n", - "\n", - "q116\n", - "\n", - "q117\n", - "\n", - "q118\n", - "\n", - "q119\n", - "\n", - "q120\n", - "\n", - "q121\n", - "\n", - "q122\n", - "\n", - "q123\n", - "\n", - "q124\n", - "\n", - "q125\n", - "\n", - "q126\n", - "\n", - "RZ(pi/2)\n", - "RZ(pi/2)\n", - "SX\n", - "SX\n", - "RZ(pi)\n", - "RZ(pi/2)\n", - "SX\n", - "\n", - "\n", - "\n", - "RZ(pi)\n", - "RZ(pi/2)\n", - "SX\n", - "SX\n", - "RZ(pi/4)\n", - "RZ(pi)\n", - "\n", - "\n", - "\n", - "SX\n", - "RZ(3*pi/4)\n", - "RZ(pi/2)\n", - "SX\n", - "\n", - "\n", - "\n", - "RZ(pi)\n", - "RZ(pi/2)\n", - "SX\n", - "SX\n", - "RZ(pi/4)\n", - "RZ(pi/2)\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "RZ(pi/2)\n", - "RZ(pi/2)\n", - "SX\n", - "SX\n", - "RZ(pi/2)\n", - "\n", - "\n", - "\n", - "RZ(pi)\n", - "RZ(pi/2)\n", - "SX\n", - "SX\n", - "RZ(pi/2)\n", - "RZ(pi/2)\n", - "\n", - "\n", - "\n", - "RZ(pi/4)\n", - "RZ(3*pi/4)\n", - "\n", - "\n", - "\n", - "SX\n", - "RZ(pi/4)\n", - "RZ(-pi/4)\n", - "RZ(pi/2)\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "c, _ := builder.New(\"toffoli\", 3).CCX(0, 1, 2).Build()\n", - "\n", - "fmt.Println(\"Before transpilation:\")\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "fmt.Printf(\"Stats: depth=%d, gates=%d, 2Q=%d\\n\\n\",\n", - "\tc.Stats().Depth, c.Stats().GateCount, c.Stats().TwoQubitGates)\n", - "\n", - "ctx := context.Background()\n", - "out, err := pipeline.Run(ctx, c, target.IBMEagle, pipeline.LevelFull)\n", - "if err != nil {\n", - "\tfmt.Println(\"Error:\", err)\n", - "} else {\n", - "\tfmt.Println(\"After transpilation (IBM Eagle, LevelFull):\")\n", - "\tgonbui.DisplayHTML(draw.SVG(out))\n", - "\tfmt.Printf(\"Stats: depth=%d, gates=%d, 2Q=%d\\n\",\n", - "\t\tout.Stats().Depth, out.Stats().GateCount, out.Stats().TwoQubitGates)\n", - "\tfmt.Println(\"\\nThe single CCX gate has been decomposed into CX + single-qubit rotations.\")\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "a7b8c9d0", - "metadata": {}, - "source": [ - "## Optimization Levels\n", - "\n", - "The transpiler supports multiple optimization levels that trade compilation\n", - "time for circuit quality:\n", - "\n", - "| Level | Name | Passes |\n", - "|-------|------|--------|\n", - "| 0 | `LevelNone` | Decompose + fix direction + validate only |\n", - "| 1 | `LevelBasic` | + routing + cancel adjacent + merge rotations |\n", - "| 2 | `LevelFull` | + commutation + parallelization |\n", - "\n", - "Higher levels produce shorter, lower-error circuits at the cost of longer\n", - "compilation time. Let's compare all three on the same Toffoli circuit." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "b8c9d0e1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Optimization Level Comparison (Toffoli on IBM Eagle)\n", - "=====================================================\n", - "Level Depth Gates 2Q\n", - "--------------------------------------------\n", - "LevelNone 41 70 9\n", - "LevelBasic 36 62 9\n", - "LevelFull 23 32 9\n", - "\n", - "Higher optimization levels reduce depth and gate count by\n", - "canceling redundant gates and merging rotations.\n" - ] - } - ], - "source": [ - "%%\n", - "c, _ := builder.New(\"toffoli\", 3).CCX(0, 1, 2).Build()\n", - "ctx := context.Background()\n", - "\n", - "fmt.Println(\"Optimization Level Comparison (Toffoli on IBM Eagle)\")\n", - "fmt.Println(\"=====================================================\")\n", - "fmt.Printf(\"%-12s %6s %6s %4s\\n\", \"Level\", \"Depth\", \"Gates\", \"2Q\")\n", - "fmt.Println(\"--------------------------------------------\")\n", - "\n", - "for _, level := range []pipeline.Level{pipeline.LevelNone, pipeline.LevelBasic, pipeline.LevelFull} {\n", - "\tout, err := pipeline.Run(ctx, c, target.IBMEagle, level)\n", - "\tif err != nil {\n", - "\t\tfmt.Printf(\"Level %d: error: %v\\n\", level, err)\n", - "\t\tcontinue\n", - "\t}\n", - "\tstats := out.Stats()\n", - "\tname := []string{\"LevelNone\", \"LevelBasic\", \"LevelFull\"}[level]\n", - "\tfmt.Printf(\"%-12s %6d %6d %4d\\n\", name, stats.Depth, stats.GateCount, stats.TwoQubitGates)\n", - "}\n", - "\n", - "fmt.Println(\"\\nHigher optimization levels reduce depth and gate count by\")\n", - "fmt.Println(\"canceling redundant gates and merging rotations.\")" - ] - }, - { - "cell_type": "markdown", - "id": "c9d0e1f2", - "metadata": {}, - "source": [ - "## Gate Decomposition: Euler Decomposition\n", - "\n", - "Any single-qubit gate (a 2x2 unitary) can be decomposed into a sequence of\n", - "three rotations using **Euler angles**. The most common convention is ZYZ:\n", - "\n", - "$$U = e^{i\\phi} \\cdot R_Z(\\alpha) \\cdot R_Y(\\beta) \\cdot R_Z(\\gamma)$$\n", - "\n", - "Different hardware targets prefer different Euler bases:\n", - "\n", - "- **IBM**: ZSX basis (RZ, SX, X) -- converts ZYZ angles to {RZ, SX} sequences.\n", - "- **Google / Rigetti**: ZXZ basis (RZ, RX).\n", - "- **Quantinuum**: ZYZ basis (RZ, RY) -- native.\n", - "\n", - "The `decompose.EulerDecompose` function decomposes any 1-qubit gate into\n", - "RZ and RY rotations (ZYZ convention). Let's decompose a U3 gate." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "d0e1f2a3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Euler Decomposition (ZYZ Convention)\n", - "=====================================\n", - "Original gate: U3(1.0000,2.0000,3.0000)\n", - "\n", - "Decomposed into:\n", - " RZ(3.0000) on qubit [0]\n", - " RY(1.0000) on qubit [0]\n", - " RZ(2.0000) on qubit [0]\n", - "\n", - "Any 1-qubit unitary can be written as RZ(alpha) . RY(beta) . RZ(gamma).\n", - "Identity rotations (angle ~ 0 mod 2pi) are automatically skipped.\n" - ] - } - ], - "source": [ - "%%\n", - "// Euler decomposition of an arbitrary single-qubit gate U3(1.0, 2.0, 3.0)\n", - "fmt.Println(\"Euler Decomposition (ZYZ Convention)\")\n", - "fmt.Println(\"=====================================\")\n", - "\n", - "g := gate.U3(1.0, 2.0, 3.0)\n", - "fmt.Printf(\"Original gate: %s\\n\\n\", g.Name())\n", - "\n", - "ops := decompose.EulerDecompose(g, 0)\n", - "fmt.Println(\"Decomposed into:\")\n", - "for _, op := range ops {\n", - "\tfmt.Printf(\" %s on qubit %v\\n\", op.Gate.Name(), op.Qubits)\n", - "}\n", - "\n", - "fmt.Println(\"\\nAny 1-qubit unitary can be written as RZ(alpha) . RY(beta) . RZ(gamma).\")\n", - "fmt.Println(\"Identity rotations (angle ~ 0 mod 2pi) are automatically skipped.\")" - ] - }, - { - "cell_type": "markdown", - "id": "e1f2a3b4", - "metadata": {}, - "source": [ - "## Gate Decomposition: KAK Decomposition\n", - "\n", - "For two-qubit gates, the **KAK (Cartan) decomposition** factorizes any 4x4\n", - "unitary into at most 3 CNOT gates plus single-qubit rotations:\n", - "\n", - "$$U = (K_{1L} \\otimes K_{1R}) \\cdot \\exp\\big(i(x\\,XX + y\\,YY + z\\,ZZ)\\big) \\cdot (K_{2L} \\otimes K_{2R})$$\n", - "\n", - "The number of nonzero Weyl parameters (x, y, z) determines the minimum\n", - "CNOT count:\n", - "\n", - "| Nonzero params | CNOTs needed | Example gates |\n", - "|:-:|:-:|:-|\n", - "| 0 | 0 | A tensor B (no entanglement) |\n", - "| 1 (= pi/4) | 1 | CNOT, CZ, CY |\n", - "| 1 (other) | 2 | Partial entanglers |\n", - "| 2-3 | 3 | SWAP, general 2Q gates |\n", - "\n", - "Let's decompose the CNOT gate matrix using KAK." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "f2a3b4c5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "KAK Decomposition of CNOT\n", - "=========================\n", - "Decomposed into:\n", - " CNOT on qubit [0 1]\n", - "Total operations: 1\n", - "\n", - "CNOT is already a minimal 1-CNOT gate (it IS a CNOT), so KAK\n", - "recognizes it and returns a single CNOT operation.\n", - "\n", - "KAK Decomposition of SWAP\n", - "=========================\n", - "Decomposed into:\n", - " CNOT on qubit [0 1]\n", - " CNOT on qubit [1 0]\n", - " CNOT on qubit [0 1]\n", - "CNOT count: 3 (the theoretical minimum for SWAP)\n" - ] - } - ], - "source": [ - "%%\n", - "// KAK decomposition of the CNOT matrix\n", - "fmt.Println(\"KAK Decomposition of CNOT\")\n", - "fmt.Println(\"=========================\")\n", - "\n", - "ops := decompose.KAK(gate.CNOT.Matrix(), 0, 1)\n", - "fmt.Println(\"Decomposed into:\")\n", - "for _, op := range ops {\n", - "\tfmt.Printf(\" %s on qubit %v\\n\", op.Gate.Name(), op.Qubits)\n", - "}\n", - "fmt.Printf(\"Total operations: %d\\n\", len(ops))\n", - "\n", - "fmt.Println(\"\\nCNOT is already a minimal 1-CNOT gate (it IS a CNOT), so KAK\")\n", - "fmt.Println(\"recognizes it and returns a single CNOT operation.\")\n", - "\n", - "// Now decompose SWAP -- should require 3 CNOTs\n", - "fmt.Println(\"\\nKAK Decomposition of SWAP\")\n", - "fmt.Println(\"=========================\")\n", - "\n", - "swapOps := decompose.KAK(gate.SWAP.Matrix(), 0, 1)\n", - "fmt.Println(\"Decomposed into:\")\n", - "cxCount := 0\n", - "for _, op := range swapOps {\n", - "\tfmt.Printf(\" %s on qubit %v\\n\", op.Gate.Name(), op.Qubits)\n", - "\tif op.Gate.Name() == \"CNOT\" {\n", - "\t\tcxCount++\n", - "\t}\n", - "}\n", - "fmt.Printf(\"CNOT count: %d (the theoretical minimum for SWAP)\\n\", cxCount)" - ] - }, - { - "cell_type": "markdown", - "id": "a3b4c5d6", - "metadata": {}, - "source": [ - "## Qubit Routing with SABRE\n", - "\n", - "On hardware with limited connectivity, a CNOT between non-adjacent qubits\n", - "cannot be executed directly. The transpiler must insert **SWAP gates** to\n", - "move qubit states to neighboring positions. Each SWAP costs 3 CNOTs, so\n", - "minimizing SWAPs is critical.\n", - "\n", - "Goqu uses the **SABRE** algorithm (Li et al., arXiv:1809.02573) for qubit\n", - "routing. SABRE is a heuristic that:\n", - "\n", - "1. Runs multiple random initial layouts in parallel.\n", - "2. Uses bidirectional iteration (forward + backward) to refine layouts.\n", - "3. Employs lookahead to choose SWAPs that help future gates, not just the\n", - " current one.\n", - "\n", - "For all-to-all targets (IonQ, Quantinuum), routing is a no-op -- every\n", - "qubit pair is directly connected." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "b4c5d6e7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Original circuit:\n", - "Stats: depth=1, gates=2, 2Q=2\n", - "\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "q3\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Routed circuit (Linear-5):\n", - "Stats: depth=1, gates=2, 2Q=2\n", - "\n", - "SWAP gates were inserted to satisfy connectivity constraints.\n", - "The CNOT(0,3) required routing because qubits 0 and 3 are not adjacent.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "q3\n", - "\n", - "q4\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Route a circuit with a non-adjacent CNOT onto a small linear topology.\n", - "// We define a 5-qubit linear chain: 0-1-2-3-4\n", - "smallLinear := target.Target{\n", - "\tName: \"Linear-5\",\n", - "\tNumQubits: 5,\n", - "\tBasisGates: []string{\"CX\", \"RZ\", \"SX\", \"X\"},\n", - "\tConnectivity: []target.QubitPair{\n", - "\t\t{0, 1}, {1, 2}, {2, 3}, {3, 4},\n", - "\t},\n", - "}\n", - "\n", - "c, _ := builder.New(\"route-demo\", 4).CNOT(0, 3).CNOT(1, 2).Build()\n", - "\n", - "fmt.Println(\"Original circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "fmt.Printf(\"Stats: depth=%d, gates=%d, 2Q=%d\\n\\n\",\n", - "\tc.Stats().Depth, c.Stats().GateCount, c.Stats().TwoQubitGates)\n", - "\n", - "routed, err := routing.Route(c, smallLinear)\n", - "if err != nil {\n", - "\tfmt.Println(\"Error:\", err)\n", - "} else {\n", - "\tfmt.Println(\"Routed circuit (Linear-5):\")\n", - "\tgonbui.DisplayHTML(draw.SVG(routed))\n", - "\tfmt.Printf(\"Stats: depth=%d, gates=%d, 2Q=%d\\n\",\n", - "\t\trouted.Stats().Depth, routed.Stats().GateCount, routed.Stats().TwoQubitGates)\n", - "\tfmt.Println(\"\\nSWAP gates were inserted to satisfy connectivity constraints.\")\n", - "\tfmt.Println(\"The CNOT(0,3) required routing because qubits 0 and 3 are not adjacent.\")\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "c5d6e7f8", - "metadata": {}, - "source": [ - "## Transpiling to Different Targets\n", - "\n", - "The same abstract circuit compiles to very different hardware-specific\n", - "circuits depending on the target. Let's transpile a Toffoli gate to three\n", - "different hardware families and compare the results.\n", - "\n", - "Key differences to watch for:\n", - "- **IBM** uses CX as its 2Q gate, with {RZ, SX, X} for 1Q.\n", - "- **Google** uses CZ as its 2Q gate, with {RZ, RX} for 1Q.\n", - "- **IonQ** uses MS (Molmer-Sorensen) as its 2Q gate, with {GPI, GPI2} for 1Q.\n", - " Because IonQ has all-to-all connectivity, no SWAP routing is needed." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "d6e7f8a9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cross-Target Transpilation: Toffoli Gate\n", - "=========================================\n", - "\n", - "Original: depth=1, gates=1, 2Q=1\n", - "\n", - "Target Depth Gates 2Q Basis\n", - "---------------------------------------------------------------\n", - "IBM Eagle 34 61 8 [CX ID RZ SX X]\n", - "Google Sycamore 34 43 6 [CZ RZ RX]\n", - "IonQ Forte 76 168 5 [GPI GPI2 MS]\n", - "\n", - "The same logical circuit produces very different physical circuits\n", - "depending on the hardware's native gate set and connectivity.\n" - ] - } - ], - "source": [ - "%%\n", - "c, _ := builder.New(\"toffoli\", 3).CCX(0, 1, 2).Build()\n", - "ctx := context.Background()\n", - "\n", - "fmt.Println(\"Cross-Target Transpilation: Toffoli Gate\")\n", - "fmt.Println(\"=========================================\")\n", - "fmt.Printf(\"\\nOriginal: depth=%d, gates=%d, 2Q=%d\\n\\n\",\n", - "\tc.Stats().Depth, c.Stats().GateCount, c.Stats().TwoQubitGates)\n", - "\n", - "crossTargets := []target.Target{\n", - "\ttarget.IBMEagle,\n", - "\ttarget.GoogleSycamore,\n", - "\ttarget.IonQForte,\n", - "}\n", - "\n", - "fmt.Printf(\"%-20s %6s %6s %4s %s\\n\", \"Target\", \"Depth\", \"Gates\", \"2Q\", \"Basis\")\n", - "fmt.Println(\"---------------------------------------------------------------\")\n", - "\n", - "for _, t := range crossTargets {\n", - "\tout, err := pipeline.Run(ctx, c, t, pipeline.LevelFull)\n", - "\tif err != nil {\n", - "\t\tfmt.Printf(\"%-20s error: %v\\n\", t.Name, err)\n", - "\t\tcontinue\n", - "\t}\n", - "\tstats := out.Stats()\n", - "\tfmt.Printf(\"%-20s %6d %6d %4d %v\\n\",\n", - "\t\tt.Name, stats.Depth, stats.GateCount, stats.TwoQubitGates, t.BasisGates)\n", - "}\n", - "\n", - "fmt.Println(\"\\nThe same logical circuit produces very different physical circuits\")\n", - "fmt.Println(\"depending on the hardware's native gate set and connectivity.\")" - ] - }, - { - "cell_type": "markdown", - "id": "e7f8a9b0", - "metadata": {}, - "source": [ - "## Individual Transpilation Passes\n", - "\n", - "The pipeline is composed of individual **passes**, each performing one\n", - "specific transformation. You can apply them individually for fine-grained\n", - "control:\n", - "\n", - "| Pass | Function | Purpose |\n", - "|------|----------|---------|\n", - "| Cancel Adjacent | `pass.CancelAdjacent` | Remove adjacent inverse pairs (H.H = I) |\n", - "| Merge Rotations | `pass.MergeRotations` | Combine RZ(a).RZ(b) into RZ(a+b) |\n", - "| Commute | `pass.CommuteThroughCNOT` | Push gates through CNOTs to enable cancellation |\n", - "| Parallelize | `pass.ParallelizeOps` | Reorder independent ops to minimize depth |\n", - "| Remove Barriers | `pass.RemoveBarriers` | Strip barrier pseudo-gates |\n", - "\n", - "Each pass has the signature `func(*ir.Circuit, target.Target) (*ir.Circuit, error)`.\n", - "\n", - "Let's see `CancelAdjacent` in action: two consecutive H gates should cancel\n", - "to identity because $H \\cdot H = I$." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "f8a9b0c1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CancelAdjacent Pass\n", - "===================\n", - "\n", - "Before:\n", - "Gates: 3\n", - "\n", - "After:\n", - "Gates: 1\n", - "\n", - "The two adjacent H gates were recognized as inverses and removed.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "H\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Cancel adjacent inverse gates: H . H = I\n", - "c, _ := builder.New(\"cancel\", 2).H(0).H(0).CNOT(0, 1).Build()\n", - "\n", - "fmt.Println(\"CancelAdjacent Pass\")\n", - "fmt.Println(\"===================\")\n", - "fmt.Println(\"\\nBefore:\")\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "fmt.Printf(\"Gates: %d\\n\\n\", c.Stats().GateCount)\n", - "\n", - "canceled, err := pass.CancelAdjacent(c, target.Simulator)\n", - "if err != nil {\n", - "\tfmt.Println(\"Error:\", err)\n", - "} else {\n", - "\tfmt.Println(\"After:\")\n", - "\tgonbui.DisplayHTML(draw.SVG(canceled))\n", - "\tfmt.Printf(\"Gates: %d\\n\", canceled.Stats().GateCount)\n", - "\tfmt.Println(\"\\nThe two adjacent H gates were recognized as inverses and removed.\")\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "a9b0c1d2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "MergeRotations Pass\n", - "===================\n", - "\n", - "Before:\n", - "Gates: 3\n", - "\n", - "After:\n", - "Gates: 1\n", - "\n", - "Three consecutive RZ gates merged into one: RZ(0.5+0.3+0.2) = RZ(1.0)\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "RZ(0.5)\n", - "RZ(0.3)\n", - "RZ(0.2)\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "RZ(1)\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// MergeRotations: combine consecutive same-axis rotations\n", - "c, _ := builder.New(\"merge\", 1).RZ(0.5, 0).RZ(0.3, 0).RZ(0.2, 0).Build()\n", - "\n", - "fmt.Println(\"MergeRotations Pass\")\n", - "fmt.Println(\"===================\")\n", - "fmt.Println(\"\\nBefore:\")\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "fmt.Printf(\"Gates: %d\\n\\n\", c.Stats().GateCount)\n", - "\n", - "merged, err := pass.MergeRotations(c, target.Simulator)\n", - "if err != nil {\n", - "\tfmt.Println(\"Error:\", err)\n", - "} else {\n", - "\tfmt.Println(\"After:\")\n", - "\tgonbui.DisplayHTML(draw.SVG(merged))\n", - "\tfmt.Printf(\"Gates: %d\\n\", merged.Stats().GateCount)\n", - "\tfmt.Println(\"\\nThree consecutive RZ gates merged into one: RZ(0.5+0.3+0.2) = RZ(1.0)\")\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "b0c1d2e3", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** The standard Toffoli gate decomposition requires several CNOT gates. How do different optimization levels (None, Basic, Full) affect the final gate count?\n", - "\n", - "**Pause and predict** before running the next cell.\n", - "\n", - "*Hint: The raw decomposition uses 6 CX gates. Think about which optimization passes (gate cancellation, commutation, merging) could reduce this count.*" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "c1d2e3f4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "How many CX gates does a Toffoli require on IBM Eagle?\n", - "======================================================\n", - "LevelNone: 9 CX gates, depth=47, total gates=82\n", - "LevelBasic: 9 CX gates, depth=35, total gates=62\n", - "LevelFull: 9 CX gates, depth=29, total gates=49\n", - "\n", - "Optimization reduces the CX count by canceling redundant gates\n", - "that appear after the initial decomposition.\n" - ] - } - ], - "source": [ - "%%\n", - "c, _ := builder.New(\"toffoli\", 3).CCX(0, 1, 2).Build()\n", - "ctx := context.Background()\n", - "\n", - "fmt.Println(\"How many CX gates does a Toffoli require on IBM Eagle?\")\n", - "fmt.Println(\"======================================================\")\n", - "\n", - "for _, level := range []pipeline.Level{pipeline.LevelNone, pipeline.LevelBasic, pipeline.LevelFull} {\n", - "\tout, err := pipeline.Run(ctx, c, target.IBMEagle, level)\n", - "\tif err != nil {\n", - "\t\tfmt.Printf(\"Level %d: error: %v\\n\", level, err)\n", - "\t\tcontinue\n", - "\t}\n", - "\tstats := out.Stats()\n", - "\tname := []string{\"LevelNone\", \"LevelBasic\", \"LevelFull\"}[level]\n", - "\tfmt.Printf(\"%s: %d CX gates, depth=%d, total gates=%d\\n\",\n", - "\t\tname, stats.TwoQubitGates, stats.Depth, stats.GateCount)\n", - "}\n", - "\n", - "fmt.Println(\"\\nOptimization reduces the CX count by canceling redundant gates\")\n", - "fmt.Println(\"that appear after the initial decomposition.\")" - ] - }, - { - "cell_type": "markdown", - "id": "k8ah2ajjjs", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. Why do trapped-ion machines need no SWAP routing while superconducting machines do?\n", - "2. How many CNOT gates does each SWAP gate cost, and why does this matter for circuit fidelity?\n", - "3. Why can the same abstract circuit produce vastly different gate counts on different hardware targets?" - ] - }, - { - "cell_type": "markdown", - "id": "d2e3f4a5", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## Exercises\n", - "\n", - "### Exercise 1 -- Cross-Target Comparison\n", - "\n", - "Build a 3-qubit QFT circuit (H, controlled-phase, SWAP) and transpile it\n", - "to IBM Eagle, Google Sycamore, and Quantinuum H1. Compare the gate counts\n", - "and depths. Which target produces the most efficient circuit, and why?" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "e3f4a5b6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and build the QFT circuit!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: Transpile a small QFT to multiple targets\n", - "// Build a 3-qubit QFT circuit: H, controlled-phase, SWAP\n", - "//\n", - "// TODO: Build the 3-qubit QFT circuit\n", - "// import \"math\"\n", - "// c, _ := builder.New(\"qft3\", 3).\n", - "// H(0).\n", - "// Ctrl(gate.Phase(math.Pi/2), []int{1}, 0).\n", - "// Ctrl(gate.Phase(math.Pi/4), []int{2}, 0).\n", - "// H(1).\n", - "// Ctrl(gate.Phase(math.Pi/2), []int{2}, 1).\n", - "// H(2).\n", - "// SWAP(0, 2).\n", - "// Build()\n", - "//\n", - "// TODO: Transpile to IBM Eagle, Google Sycamore, and Quantinuum H1\n", - "// For each target:\n", - "// out, err := pipeline.Run(ctx, c, target.???, pipeline.LevelFull)\n", - "// Print depth, gate count, and 2Q gate count\n", - "//\n", - "// TODO: Compare results and explain why all-to-all targets\n", - "// (Quantinuum) avoid SWAP routing overhead\n", - "_ = context.Background()\n", - "fmt.Println(\"Uncomment the code above and build the QFT circuit!\")" - ] - }, - { - "cell_type": "markdown", - "id": "f4a5b6c7", - "metadata": {}, - "source": [ - "### Exercise 2 -- Verify Transpilation Preserves Functionality\n", - "\n", - "Transpilation should never change **what** a circuit computes -- only\n", - "**how** it computes it. Use `verify.Equivalent` to confirm that the\n", - "transpiled circuit produces the same unitary as the original." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "a5b6c7d8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Uncomment the code above and verify transpilation!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: Verify transpilation preserves the unitary\n", - "// Transpile a Toffoli gate to multiple targets and confirm equivalence\n", - "//\n", - "// TODO: Build a Toffoli circuit\n", - "// c, _ := builder.New(\"toffoli\", 3).CCX(0, 1, 2).Build()\n", - "//\n", - "// TODO: For each target (IBM Eagle, Google Sycamore, IonQ Forte):\n", - "// 1. Transpile with pipeline.Run at LevelFull\n", - "// 2. Verify equivalence using verify.Equivalent(c, out, 1e-6)\n", - "// 3. Print whether the circuits are equivalent and the gate stats\n", - "//\n", - "// for _, t := range []target.Target{target.IBMEagle, target.GoogleSycamore, target.IonQForte} {\n", - "// out, err := pipeline.Run(ctx, c, t, pipeline.LevelFull)\n", - "// eq, err := verify.Equivalent(c, out, ???)\n", - "// fmt.Printf(\"%s: equivalent=%v (gates=%d, 2Q=%d)\\n\",\n", - "// t.Name, eq, out.Stats().GateCount, out.Stats().TwoQubitGates)\n", - "// }\n", - "_ = context.Background()\n", - "fmt.Println(\"Uncomment the code above and verify transpilation!\")" - ] - }, - { - "cell_type": "markdown", - "id": "b6c7d8e9", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "\n", - "1. **Real hardware has constraints**: each processor supports only a few\n", - " basis gates and has limited qubit connectivity. Transpilation bridges\n", - " the gap between abstract circuits and physical execution.\n", - "\n", - "2. **Gate decomposition** breaks high-level gates into basis gates. Euler\n", - " decomposition handles single-qubit gates (at most 3 rotations); KAK\n", - " decomposition handles two-qubit gates (at most 3 CNOTs).\n", - "\n", - "3. **Qubit routing** (SABRE) inserts SWAP gates to satisfy connectivity.\n", - " Each SWAP costs 3 CNOTs, so all-to-all targets (trapped ions) have a\n", - " significant advantage for circuits with long-range interactions.\n", - "\n", - "4. **Optimization levels** trade compilation time for circuit quality.\n", - " Higher levels (LevelFull) apply more passes: cancellation, merging,\n", - " commutation, and parallelization.\n", - "\n", - "5. **Individual passes** give fine-grained control. `CancelAdjacent`\n", - " removes inverse pairs, `MergeRotations` combines same-axis rotations,\n", - " `CommuteThroughCNOT` moves gates through entangling gates, and\n", - " `ParallelizeOps` reorders for minimum depth.\n", - "\n", - "6. **Transpilation preserves functionality**: the `verify` package confirms\n", - " that transpiled circuits compute the same unitary as the original,\n", - " up to global phase.\n", - "\n", - "7. **Target choice matters**: the same abstract circuit can produce vastly\n", - " different physical circuits depending on the hardware. Understanding\n", - " your target's basis gates and connectivity is essential for writing\n", - " efficient quantum programs.\n", - "\n", - "---\n", - "\n", - "**Next up:** Notebook 13 — Variational Algorithms, where we'll build parameterized circuits and use classical-quantum hybrid loops for VQE and QAOA." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/13-variational-algorithms.ipynb b/notebooks/13-variational-algorithms.ipynb deleted file mode 100644 index a120a76..0000000 --- a/notebooks/13-variational-algorithms.ipynb +++ /dev/null @@ -1,1370 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a1b2c3d4", - "metadata": {}, - "source": [ - "# Notebook 13 -- Variational Algorithms (VQE and QAOA)\n", - "\n", - "**Prerequisites:** Notebooks 01-06 and 11. Familiarity with gates, measurement, and noise.\n", - "\n", - "Variational quantum algorithms are the **workhorse of the NISQ era**.\n", - "Instead of requiring deep circuits with thousands of perfect gates, they\n", - "use **shallow parameterized circuits** whose parameters are tuned by a\n", - "classical optimizer. This hybrid quantum-classical loop lets us extract\n", - "useful results from today's noisy, limited-qubit hardware.\n", - "\n", - "The key insight is the **variational principle**: for any trial state\n", - "$|\\psi(\\theta)\\rangle$, the expectation value\n", - "$\\langle\\psi(\\theta)|H|\\psi(\\theta)\\rangle \\geq E_0$ is an upper bound\n", - "on the true ground-state energy $E_0$. By minimizing over $\\theta$, we\n", - "get the best approximation to the ground state that our circuit can\n", - "express.\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Describe** the variational principle and the classical-quantum hybrid optimization loop.\n", - "2. **Implement** VQE to find the ground state energy of a Hamiltonian.\n", - "3. **Implement** QAOA to solve a MaxCut problem.\n", - "4. **Compare** different optimizers and gradient methods.\n", - "\n", - "In this notebook we will:\n", - "\n", - "1. Build **parameterized circuits** with symbolic parameters.\n", - "2. Explore **ansatz templates** -- pre-built circuit structures for\n", - " variational algorithms.\n", - "3. Run the **Variational Quantum Eigensolver (VQE)** to find the ground\n", - " state energy of a molecular Hamiltonian.\n", - "4. Run the **Quantum Approximate Optimization Algorithm (QAOA)** to solve\n", - " a combinatorial optimization problem (MaxCut).\n", - "5. Compare **classical optimizers** (Nelder-Mead, SPSA, Adam, L-BFGS).\n", - "6. Compute **quantum gradients** via parameter-shift and finite differences.\n", - "7. Use **parameter sweeps** to visualize energy landscapes.\n", - "\n", - "### Misconception: Variational algorithms always find the global minimum\n", - "\n", - "Variational algorithms are **not guaranteed** to converge to the true\n", - "ground state. The quality of the result depends on (a) the expressibility\n", - "of the ansatz -- can it even represent the target state? -- and (b) the\n", - "optimization landscape, which may contain local minima and barren plateaus.\n", - "Careful ansatz design and optimizer choice are essential." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "b2c3d4e5", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"context\"\n", - "\t\"fmt\"\n", - "\t\"math\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/algorithm/ansatz\"\n", - "\t\"github.com/splch/goqu/algorithm/gradient\"\n", - "\t\"github.com/splch/goqu/algorithm/optim\"\n", - "\t\"github.com/splch/goqu/algorithm/qaoa\"\n", - "\t\"github.com/splch/goqu/algorithm/vqe\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/circuit/ir\"\n", - "\t\"github.com/splch/goqu/circuit/param\"\n", - "\t\"github.com/splch/goqu/sim/pauli\"\n", - "\t\"github.com/splch/goqu/sweep\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "c3d4e5f6", - "metadata": {}, - "source": [ - "## Parameterized Circuits and Symbolic Parameters\n", - "\n", - "A **parameterized circuit** contains gates whose angles are not fixed\n", - "numbers but **symbolic parameters** -- named placeholders that can be\n", - "bound to concrete values later. This is the foundation of all variational\n", - "algorithms: we build a circuit template once, then evaluate it at many\n", - "different parameter values during optimization.\n", - "\n", - "In Goqu, `param.New(name)` creates a standalone symbolic parameter, and\n", - "`p.Expr()` returns an expression that can be passed to symbolic gate\n", - "builders like `b.SymRY(expr, qubit)`. After building the circuit,\n", - "`ir.FreeParameters(c)` lists all unbound parameter names, and\n", - "`ir.Bind(c, bindings)` substitutes concrete values to produce a\n", - "standard circuit that can be simulated.\n", - "\n", - "Let's create a simple 1-qubit parameterized circuit with a single\n", - "RY rotation." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "d4e5f6a7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Free parameters: [theta]\n", - "\n", - "Parameterized circuit:\n", - "After binding theta = pi/4:\n", - "Free parameters after binding: []\n", - "\n", - "The symbolic placeholder has been replaced with a concrete rotation angle.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "RY\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "RY(pi/4)\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Create a symbolic parameter\n", - "theta := param.New(\"theta\")\n", - "\n", - "// Build a parameterized circuit with a symbolic RY gate\n", - "b := builder.New(\"param-circuit\", 1)\n", - "b.SymRY(theta.Expr(), 0)\n", - "c, err := b.Build()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "// Inspect the free parameters\n", - "fmt.Println(\"Free parameters:\", ir.FreeParameters(c))\n", - "fmt.Println()\n", - "fmt.Println(\"Parameterized circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "\n", - "// Bind the parameter to a concrete value and inspect the result\n", - "bound, err := ir.Bind(c, map[string]float64{\"theta\": math.Pi / 4})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"After binding theta = pi/4:\")\n", - "gonbui.DisplayHTML(draw.SVG(bound))\n", - "fmt.Println(\"Free parameters after binding:\", ir.FreeParameters(bound))\n", - "fmt.Println(\"\\nThe symbolic placeholder has been replaced with a concrete rotation angle.\")" - ] - }, - { - "cell_type": "markdown", - "id": "e5f6a7b8", - "metadata": {}, - "source": [ - "## Ansatz Templates\n", - "\n", - "An **ansatz** (German for \"approach\") is a pre-built parameterized circuit\n", - "template designed for variational algorithms. The choice of ansatz\n", - "determines:\n", - "\n", - "- **Expressibility**: which states the circuit can represent.\n", - "- **Trainability**: whether the optimizer can find good parameters (or\n", - " gets stuck in barren plateaus).\n", - "- **Hardware efficiency**: how many gates and how much depth the circuit\n", - " requires.\n", - "\n", - "Goqu provides several standard ansatze:\n", - "\n", - "| Ansatz | Rotations | Params/qubit/rep | Best for |\n", - "|:---|:---|:---:|:---|\n", - "| **RealAmplitudes** | RY | 1 | Real-valued wavefunctions (chemistry) |\n", - "| **EfficientSU2** | RY + RZ | 2 | General states (full SU(2) coverage) |\n", - "| **StronglyEntanglingLayers** | Rot(phi,theta,omega) | 3 | Maximum expressibility |\n", - "| **BasicEntanglerLayers** | RX | 1 | Simple problems, fast training |\n", - "\n", - "Each ansatz also supports different **entanglement patterns**: `Linear`\n", - "(nearest-neighbor CNOTs), `Full` (all-to-all CNOTs), and `Circular`\n", - "(linear + wraparound). Let's inspect two of them." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "f6a7b8c9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "RealAmplitudes (2 qubits, 2 reps, Linear entanglement):\n", - "Parameters: 6\n", - "Free params: [θ[0] θ[1] θ[2] θ[3] θ[4] θ[5]]\n", - "\n", - "EfficientSU2 (2 qubits, 2 reps, Full entanglement):\n", - "Parameters: 12\n", - "\n", - "EfficientSU2 has twice as many parameters as RealAmplitudes\n", - "because it uses both RY and RZ rotations per qubit per layer.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "RY\n", - "RY\n", - "\n", - "\n", - "\n", - "RY\n", - "RY\n", - "\n", - "\n", - "\n", - "RY\n", - "RY\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "RY\n", - "RZ\n", - "RY\n", - "RZ\n", - "\n", - "\n", - "\n", - "RY\n", - "RZ\n", - "RY\n", - "RZ\n", - "\n", - "\n", - "\n", - "RY\n", - "RZ\n", - "RY\n", - "RZ\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// RealAmplitudes: RY rotations + CNOT entanglement\n", - "ra := ansatz.NewRealAmplitudes(2, 2, ansatz.Linear)\n", - "raCircuit, err := ra.Circuit()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "fmt.Println(\"RealAmplitudes (2 qubits, 2 reps, Linear entanglement):\")\n", - "gonbui.DisplayHTML(draw.SVG(raCircuit))\n", - "fmt.Printf(\"Parameters: %d\\n\", ra.NumParams())\n", - "fmt.Printf(\"Free params: %v\\n\\n\", ir.FreeParameters(raCircuit))\n", - "\n", - "// EfficientSU2: RY + RZ rotations + CNOT entanglement\n", - "esu2 := ansatz.NewEfficientSU2(2, 2, ansatz.Full)\n", - "esu2Circuit, err := esu2.Circuit()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "fmt.Println(\"EfficientSU2 (2 qubits, 2 reps, Full entanglement):\")\n", - "gonbui.DisplayHTML(draw.SVG(esu2Circuit))\n", - "fmt.Printf(\"Parameters: %d\\n\", esu2.NumParams())\n", - "\n", - "fmt.Println(\"\\nEfficientSU2 has twice as many parameters as RealAmplitudes\")\n", - "fmt.Println(\"because it uses both RY and RZ rotations per qubit per layer.\")" - ] - }, - { - "cell_type": "markdown", - "id": "a7b8c9d0", - "metadata": {}, - "source": [ - "## VQE -- Variational Quantum Eigensolver\n", - "\n", - "VQE finds the **ground state energy** of a Hamiltonian $H$ by variationally\n", - "minimizing the expectation value $\\langle\\psi(\\theta)|H|\\psi(\\theta)\\rangle$.\n", - "\n", - "The algorithm:\n", - "\n", - "1. **Prepare** a trial state $|\\psi(\\theta)\\rangle$ using a parameterized\n", - " circuit (ansatz).\n", - "2. **Measure** the expectation value $\\langle H \\rangle$ -- the energy.\n", - "3. **Optimize** the parameters $\\theta$ using a classical optimizer to\n", - " minimize the energy.\n", - "4. **Repeat** until convergence.\n", - "\n", - "By the variational principle, the final energy is an **upper bound** on\n", - "the true ground state energy $E_0$. The tighter the bound, the better\n", - "the ansatz and optimizer performed." - ] - }, - { - "cell_type": "markdown", - "id": "b8c9d0e1", - "metadata": {}, - "source": [ - "### Building a Hamiltonian: H$_2$ Molecule\n", - "\n", - "The Hamiltonian of a physical system is expressed as a weighted sum of\n", - "Pauli strings (tensor products of I, X, Y, Z). For the hydrogen molecule\n", - "H$_2$ at its equilibrium bond length (~0.735 angstroms), the electronic\n", - "Hamiltonian in the STO-3G basis with Bravyi-Kitaev mapping reduces to\n", - "a 2-qubit Pauli sum:\n", - "\n", - "$$H_{\\text{H}_2} = c_0 \\cdot II + c_1 \\cdot IZ + c_2 \\cdot ZI + c_3 \\cdot ZZ + c_4 \\cdot XX$$\n", - "\n", - "where the coefficients come from the molecular integrals. The XX term\n", - "captures the off-diagonal electron correlation that makes this problem\n", - "non-trivial for classical computation. The exact ground state energy\n", - "for this Hamiltonian is approximately $-1.137$ Hartree.\n", - "\n", - "Let's build this Hamiltonian and run VQE." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "c9d0e1f2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "H2 Hamiltonian:\n", - " {[{(-1.0523+0i) [0 0] 2} {(0.3979+0i) [0 1] 2} {(-0.3979+0i) [1 0] 2} {(-0.0112+0i) [1 1] 2} {(0.1809+0i) [2 2] 2}] 2}\n", - " Qubits: 2\n", - "\n", - "Ground state energy: -1.857202 Hartree\n", - "Converged: true\n", - "Iterations: 184\n", - "Function evaluations: 319\n", - "Optimal parameters: [0.33446917036168455 -0.8292977058598237 0.25088223945202737 3.99926582915184]\n", - "\n", - "Exact ground state energy ~ -1.137 Hartree\n", - "VQE error: -0.720202 Hartree\n" - ] - } - ], - "source": [ - "%%\n", - "// H2 Hamiltonian at equilibrium bond length (STO-3G, Bravyi-Kitaev mapping)\n", - "// H = -1.0523*II + 0.3979*IZ - 0.3979*ZI - 0.0112*ZZ + 0.1809*XX\n", - "xx, _ := pauli.Parse(\"XX\")\n", - "zz, _ := pauli.Parse(\"ZZ\")\n", - "zi, _ := pauli.Parse(\"ZI\")\n", - "iz, _ := pauli.Parse(\"IZ\")\n", - "ii, _ := pauli.Parse(\"II\")\n", - "\n", - "h2, err := pauli.NewPauliSum([]pauli.PauliString{\n", - "\tii.Scale(-1.0523),\n", - "\tiz.Scale(0.3979),\n", - "\tzi.Scale(-0.3979),\n", - "\tzz.Scale(-0.0112),\n", - "\txx.Scale(0.1809),\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"H2 Hamiltonian:\")\n", - "fmt.Println(\" \", h2)\n", - "fmt.Printf(\" Qubits: %d\\n\\n\", h2.NumQubits())\n", - "\n", - "// Run VQE with RealAmplitudes ansatz and Nelder-Mead optimizer\n", - "ctx := context.Background()\n", - "ra := ansatz.NewRealAmplitudes(2, 1, ansatz.Linear)\n", - "\n", - "result, err := vqe.Run(ctx, vqe.Config{\n", - "\tHamiltonian: h2,\n", - "\tAnsatz: ra,\n", - "\tOptimizer: &optim.NelderMead{},\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"Ground state energy: %.6f Hartree\\n\", result.Energy)\n", - "fmt.Printf(\"Converged: %v\\n\", result.Converged)\n", - "fmt.Printf(\"Iterations: %d\\n\", result.NumIters)\n", - "fmt.Printf(\"Function evaluations: %d\\n\", result.NumEvals)\n", - "fmt.Printf(\"Optimal parameters: %v\\n\", result.OptimalParams)\n", - "fmt.Printf(\"\\nExact ground state energy ~ -1.137 Hartree\\n\")\n", - "fmt.Printf(\"VQE error: %.6f Hartree\\n\", result.Energy-(-1.137))" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "d0e1f2a3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "VQE Convergence History\n", - "=======================\n", - " Eval Energy\n", - "--------------------\n", - " 1 -1.063500\n", - " 33 -1.282603\n", - " 65 -1.562761\n", - " 97 -1.650954\n", - " 129 -1.840572\n", - " 161 -1.844801\n", - " 193 -1.844992\n", - " 225 -1.851910\n", - " 257 -1.857141\n", - " 289 -1.857199\n", - " 319 -1.857202\n", - "\n", - "Total evaluations: 319\n", - "Final energy: -1.857202\n", - "\n", - "The energy decreases toward the ground state (-1.137 Hartree).\n", - "By the variational principle, the result is always >= the exact ground state.\n" - ] - } - ], - "source": [ - "%%\n", - "// Visualize the VQE convergence history\n", - "xx, _ := pauli.Parse(\"XX\")\n", - "zz, _ := pauli.Parse(\"ZZ\")\n", - "zi, _ := pauli.Parse(\"ZI\")\n", - "iz, _ := pauli.Parse(\"IZ\")\n", - "ii, _ := pauli.Parse(\"II\")\n", - "\n", - "h2, _ := pauli.NewPauliSum([]pauli.PauliString{\n", - "\tii.Scale(-1.0523),\n", - "\tiz.Scale(0.3979),\n", - "\tzi.Scale(-0.3979),\n", - "\tzz.Scale(-0.0112),\n", - "\txx.Scale(0.1809),\n", - "})\n", - "\n", - "ctx := context.Background()\n", - "ra := ansatz.NewRealAmplitudes(2, 1, ansatz.Linear)\n", - "\n", - "result, err := vqe.Run(ctx, vqe.Config{\n", - "\tHamiltonian: h2,\n", - "\tAnsatz: ra,\n", - "\tOptimizer: &optim.NelderMead{},\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"VQE Convergence History\")\n", - "fmt.Println(\"=======================\")\n", - "fmt.Printf(\"%6s %12s\\n\", \"Eval\", \"Energy\")\n", - "fmt.Println(\"--------------------\")\n", - "\n", - "// Show every 10th evaluation to keep output manageable\n", - "for i, e := range result.History {\n", - "\tif i%(len(result.History)/10+1) == 0 || i == len(result.History)-1 {\n", - "\t\tfmt.Printf(\"%6d %12.6f\\n\", i+1, e)\n", - "\t}\n", - "}\n", - "\n", - "fmt.Printf(\"\\nTotal evaluations: %d\\n\", len(result.History))\n", - "fmt.Printf(\"Final energy: %.6f\\n\", result.History[len(result.History)-1])\n", - "fmt.Println(\"\\nThe energy decreases toward the ground state (-1.137 Hartree).\")\n", - "fmt.Println(\"By the variational principle, the result is always >= the exact ground state.\")" - ] - }, - { - "cell_type": "markdown", - "id": "e1f2a3b4", - "metadata": {}, - "source": [ - "## QAOA -- Quantum Approximate Optimization Algorithm\n", - "\n", - "While VQE targets physics problems (finding ground states), **QAOA**\n", - "targets **combinatorial optimization** problems -- finding the minimum\n", - "of a cost function over binary strings.\n", - "\n", - "QAOA builds a circuit with $p$ alternating layers:\n", - "\n", - "1. **Cost layer**: $e^{-i \\gamma_k C}$ -- encodes the problem.\n", - "2. **Mixer layer**: $e^{-i \\beta_k B}$ -- explores the solution space.\n", - "\n", - "The circuit starts in $|+\\rangle^{\\otimes n}$ (uniform superposition over\n", - "all bitstrings) and the angles $(\\gamma_1, \\ldots, \\gamma_p,\n", - "\\beta_1, \\ldots, \\beta_p)$ are optimized classically. As $p \\to \\infty$,\n", - "QAOA is guaranteed to converge to the optimal solution (by the quantum\n", - "adiabatic theorem), but in practice we use small $p$ on NISQ devices.\n", - "\n", - "### MaxCut Problem\n", - "\n", - "A classic application of QAOA is **MaxCut**: given a graph, partition\n", - "the vertices into two sets to maximize the number of edges crossing the\n", - "partition. The cost Hamiltonian is:\n", - "\n", - "$$C = \\sum_{(i,j) \\in E} \\frac{1}{2}(I - Z_i Z_j)$$\n", - "\n", - "Each edge contributes $+1$ when vertices $i$ and $j$ are in different\n", - "partitions and $0$ when they are in the same partition." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "f2a3b4c5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "MaxCut Hamiltonian (triangle graph):\n", - " {[{(-0.5+0i) [0 0 0] 3} {(0.5+0i) [1 1 0] 3} {(-0.5+0i) [0 0 0] 3} {(0.5+0i) [0 1 1] 3} {(-0.5+0i) [0 0 0] 3} {(0.5+0i) [1 0 1] 3}] 3}\n", - " Qubits: 3\n", - "\n", - "Best bitstring: 100\n", - "Best cost: -2.00\n", - "Optimal value: -2.0000\n", - "Converged: true, Iterations: 60\n", - "\n", - "For a triangle (K3), MaxCut = 2 (any partition cutting 2 of 3 edges).\n", - "Valid maxcut bitstrings: 001, 010, 100, 110, 101, 011.\n", - "QAOA should find one of these.\n" - ] - } - ], - "source": [ - "%%\n", - "// QAOA for MaxCut on a triangle graph: edges (0,1), (1,2), (0,2)\n", - "edges := [][2]int{{0, 1}, {1, 2}, {0, 2}}\n", - "costH, err := qaoa.MaxCutHamiltonian(edges, 3)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"MaxCut Hamiltonian (triangle graph):\")\n", - "fmt.Println(\" \", costH)\n", - "fmt.Printf(\" Qubits: %d\\n\\n\", costH.NumQubits())\n", - "\n", - "ctx := context.Background()\n", - "qaoaResult, err := qaoa.Run(ctx, qaoa.Config{\n", - "\tCostHamiltonian: costH,\n", - "\tLayers: 2,\n", - "\tOptimizer: &optim.NelderMead{},\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"Best bitstring: %s\\n\", qaoaResult.BestBitstring)\n", - "fmt.Printf(\"Best cost: %.2f\\n\", qaoaResult.BestCost)\n", - "fmt.Printf(\"Optimal value: %.4f\\n\", qaoaResult.OptimalValue)\n", - "fmt.Printf(\"Converged: %v, Iterations: %d\\n\", qaoaResult.Converged, qaoaResult.NumIters)\n", - "\n", - "fmt.Println(\"\\nFor a triangle (K3), MaxCut = 2 (any partition cutting 2 of 3 edges).\")\n", - "fmt.Println(\"Valid maxcut bitstrings: 001, 010, 100, 110, 101, 011.\")\n", - "fmt.Println(\"QAOA should find one of these.\")" - ] - }, - { - "cell_type": "markdown", - "id": "a3b4c5d6", - "metadata": {}, - "source": [ - "## Optimizers\n", - "\n", - "The choice of classical optimizer significantly affects VQE and QAOA\n", - "performance. Goqu provides four optimizers:\n", - "\n", - "| Optimizer | Type | Evals/iter | Best for |\n", - "|:---|:---|:---:|:---|\n", - "| **Nelder-Mead** | Gradient-free (simplex) | $n+1$ | Small parameter spaces, no gradient available |\n", - "| **SPSA** | Gradient-free (stochastic) | 2 | Noisy cost functions, large parameter spaces |\n", - "| **Adam** | Gradient-based | 1 + gradient | Smooth landscapes, many parameters |\n", - "| **L-BFGS** | Gradient-based (quasi-Newton) | 1 + gradient | Smooth landscapes, fast convergence |\n", - "\n", - "Gradient-free methods (Nelder-Mead, SPSA) only need the objective value.\n", - "Gradient-based methods (Adam, L-BFGS) also need the gradient\n", - "$\\nabla_\\theta \\langle H \\rangle$, which can be computed via parameter-shift\n", - "or finite differences. Let's compare all four on the same H$_2$ VQE problem." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "b4c5d6e7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Optimizer Comparison on H2 VQE\n", - "==============================\n", - "Optimizer Energy Iters Converged Error\n", - "--------------------------------------------------------------\n", - "Nelder-Mead -1.857202 184 true -0.720202\n", - "SPSA -1.239421 1000 false -0.102421\n", - "Adam -1.244400 133 true -0.107400\n", - "L-BFGS -1.244400 6 true -0.107400\n", - "\n", - "Gradient-based methods (Adam, L-BFGS) typically converge faster\n", - "but require computing the gradient at each step.\n" - ] - } - ], - "source": [ - "%%\n", - "// Compare optimizers on the H2 VQE problem\n", - "ctx := context.Background()\n", - "\n", - "// H2 Hamiltonian (correct 5-term version)\n", - "xx, _ := pauli.Parse(\"XX\")\n", - "zz, _ := pauli.Parse(\"ZZ\")\n", - "zi, _ := pauli.Parse(\"ZI\")\n", - "iz, _ := pauli.Parse(\"IZ\")\n", - "ii, _ := pauli.Parse(\"II\")\n", - "\n", - "h2, _ := pauli.NewPauliSum([]pauli.PauliString{\n", - "\tii.Scale(-1.0523),\n", - "\tiz.Scale(0.3979),\n", - "\tzi.Scale(-0.3979),\n", - "\tzz.Scale(-0.0112),\n", - "\txx.Scale(0.1809),\n", - "})\n", - "\n", - "// Build gradient function for gradient-based optimizers\n", - "raOpt := ansatz.NewRealAmplitudes(2, 1, ansatz.Linear)\n", - "raOptCircuit, _ := raOpt.Circuit()\n", - "paramNames := ir.FreeParameters(raOptCircuit)\n", - "costFn := gradient.CostFunc(raOptCircuit, h2, paramNames)\n", - "psGrad := gradient.ParameterShift(costFn)\n", - "\n", - "type optimEntry struct {\n", - "\tname string\n", - "\topt optim.Optimizer\n", - "\tgrad optim.GradientFunc\n", - "}\n", - "\n", - "optimizers := []optimEntry{\n", - "\t{\"Nelder-Mead\", &optim.NelderMead{}, nil},\n", - "\t{\"SPSA\", &optim.SPSA{}, nil},\n", - "\t{\"Adam\", &optim.Adam{LR: 0.1}, psGrad},\n", - "\t{\"L-BFGS\", &optim.LBFGS{}, psGrad},\n", - "}\n", - "\n", - "fmt.Println(\"Optimizer Comparison on H2 VQE\")\n", - "fmt.Println(\"==============================\")\n", - "fmt.Printf(\"%-14s %12s %6s %8s %10s\\n\", \"Optimizer\", \"Energy\", \"Iters\", \"Converged\", \"Error\")\n", - "fmt.Println(\"--------------------------------------------------------------\")\n", - "\n", - "for _, entry := range optimizers {\n", - "\tra := ansatz.NewRealAmplitudes(2, 1, ansatz.Linear)\n", - "\tres, err := vqe.Run(ctx, vqe.Config{\n", - "\t\tHamiltonian: h2,\n", - "\t\tAnsatz: ra,\n", - "\t\tOptimizer: entry.opt,\n", - "\t\tGradient: entry.grad,\n", - "\t})\n", - "\tif err != nil {\n", - "\t\tfmt.Printf(\"%-14s error: %v\\n\", entry.name, err)\n", - "\t\tcontinue\n", - "\t}\n", - "\tfmt.Printf(\"%-14s %12.6f %6d %8v %10.6f\\n\",\n", - "\t\tentry.name, res.Energy, res.NumIters, res.Converged, res.Energy-(-1.137))\n", - "}\n", - "\n", - "fmt.Println(\"\\nGradient-based methods (Adam, L-BFGS) typically converge faster\")\n", - "fmt.Println(\"but require computing the gradient at each step.\")" - ] - }, - { - "cell_type": "markdown", - "id": "c5d6e7f8", - "metadata": {}, - "source": [ - "## Gradient Methods\n", - "\n", - "Gradient-based optimizers need $\\nabla_\\theta \\langle H \\rangle$.\n", - "On a quantum computer, we cannot directly differentiate a quantum circuit,\n", - "but we have two strategies:\n", - "\n", - "### Parameter-Shift Rule\n", - "\n", - "For gates of the form $e^{-i\\theta G/2}$ where $G$ has eigenvalues\n", - "$\\pm 1$ (which includes RX, RY, RZ), the **exact** gradient is:\n", - "\n", - "$$\\frac{\\partial}{\\partial \\theta_j} \\langle H \\rangle =\n", - "\\frac{1}{2} \\left[ f(\\theta_j + \\pi/2) - f(\\theta_j - \\pi/2) \\right]$$\n", - "\n", - "This requires **2N function evaluations** per gradient (N = number of\n", - "parameters), but gives the **exact analytical gradient**.\n", - "\n", - "### Finite Differences\n", - "\n", - "A simpler but approximate method:\n", - "\n", - "$$\\frac{\\partial}{\\partial \\theta_j} \\langle H \\rangle \\approx\n", - "\\frac{f(\\theta_j + h) - f(\\theta_j - h)}{2h}$$\n", - "\n", - "This also needs 2N evaluations but introduces a bias controlled by $h$.\n", - "Smaller $h$ reduces bias but increases sensitivity to shot noise." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "d6e7f8a9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Gradient Comparison at test point\n", - "=================================\n", - "Cost at test point: -1.273360\n", - "\n", - "Parameter Param-Shift Finite-Diff Difference\n", - "----------------------------------------------------------\n", - "θ[0] 0.36589862 0.36589862 1.47e-11\n", - "θ[1] -0.14400701 -0.14400701 1.39e-11\n", - "θ[2] 0.38766675 0.38766675 2.49e-12\n", - "θ[3] -0.19716349 -0.19716349 1.22e-11\n", - "\n", - "Both methods agree to high precision.\n", - "Parameter-shift gives the exact gradient; finite differences are approximate.\n", - "On real hardware with shot noise, parameter-shift is generally preferred.\n" - ] - } - ], - "source": [ - "%%\n", - "// Compare parameter-shift and finite-difference gradients\n", - "xx, _ := pauli.Parse(\"XX\")\n", - "zz, _ := pauli.Parse(\"ZZ\")\n", - "zi, _ := pauli.Parse(\"ZI\")\n", - "iz, _ := pauli.Parse(\"IZ\")\n", - "ii, _ := pauli.Parse(\"II\")\n", - "\n", - "h2, _ := pauli.NewPauliSum([]pauli.PauliString{\n", - "\tii.Scale(-1.0523),\n", - "\tiz.Scale(0.3979),\n", - "\tzi.Scale(-0.3979),\n", - "\tzz.Scale(-0.0112),\n", - "\txx.Scale(0.1809),\n", - "})\n", - "\n", - "raGrad := ansatz.NewRealAmplitudes(2, 1, ansatz.Linear)\n", - "raGradCircuit, _ := raGrad.Circuit()\n", - "paramNames := ir.FreeParameters(raGradCircuit)\n", - "costFn := gradient.CostFunc(raGradCircuit, h2, paramNames)\n", - "\n", - "// Create both gradient methods\n", - "psGrad := gradient.ParameterShift(costFn)\n", - "fdGrad := gradient.FiniteDifference(costFn, 1e-5)\n", - "\n", - "// Evaluate at a test point\n", - "testPoint := make([]float64, len(paramNames))\n", - "for i := range testPoint {\n", - "\ttestPoint[i] = 0.5 * float64(i+1)\n", - "}\n", - "\n", - "psResult := psGrad(testPoint)\n", - "fdResult := fdGrad(testPoint)\n", - "\n", - "fmt.Println(\"Gradient Comparison at test point\")\n", - "fmt.Println(\"=================================\")\n", - "fmt.Printf(\"Cost at test point: %.6f\\n\\n\", costFn(testPoint))\n", - "\n", - "fmt.Printf(\"%-12s %14s %14s %14s\\n\", \"Parameter\", \"Param-Shift\", \"Finite-Diff\", \"Difference\")\n", - "fmt.Println(\"----------------------------------------------------------\")\n", - "\n", - "for i, name := range paramNames {\n", - "\tdiff := math.Abs(psResult[i] - fdResult[i])\n", - "\tfmt.Printf(\"%-12s %14.8f %14.8f %14.2e\\n\", name, psResult[i], fdResult[i], diff)\n", - "}\n", - "\n", - "fmt.Println(\"\\nBoth methods agree to high precision.\")\n", - "fmt.Println(\"Parameter-shift gives the exact gradient; finite differences are approximate.\")\n", - "fmt.Println(\"On real hardware with shot noise, parameter-shift is generally preferred.\")" - ] - }, - { - "cell_type": "markdown", - "id": "e7f8a9b0", - "metadata": {}, - "source": [ - "## Parameter Sweeps\n", - "\n", - "To understand how the energy depends on a single parameter, we can\n", - "**sweep** it across a range of values while holding everything else\n", - "fixed. This creates an **energy landscape** -- a 1D slice through the\n", - "high-dimensional parameter space.\n", - "\n", - "Goqu's `sweep.Linspace` generates evenly-spaced values for a named\n", - "parameter, and `sweep.RunSim` evaluates the parameterized circuit at\n", - "each point using statevector simulation.\n", - "\n", - "Energy landscapes reveal important features:\n", - "- **Global minima** where the optimizer should converge.\n", - "- **Local minima** that can trap gradient-based methods.\n", - "- **Periodicity** from the underlying rotation gates." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "f8a9b0c1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Parameter Sweep: RY(theta) on |0>\n", - "===================================\n", - " theta P(|0>) P(|1>)\n", - "--------------------------\n", - " 0.000 1.000 0.000\n", - " 0.641 0.902 0.098\n", - " 1.282 0.641 0.359\n", - " 1.923 0.318 0.682\n", - " 2.565 0.077 0.923\n", - " 3.206 0.000 1.000\n", - " 3.847 0.116 0.884\n", - " 4.488 0.397 0.603\n", - " 5.129 0.664 0.336\n", - " 5.770 0.940 0.060\n", - " 6.283 1.000 0.000\n", - "\n", - "RY(theta)|0> = cos(theta/2)|0> + sin(theta/2)|1>\n", - "At theta=0: pure |0>. At theta=pi: pure |1>. At theta=2pi: back to |0>.\n" - ] - } - ], - "source": [ - "%%\n", - "// Sweep a single parameter of a 1-qubit parameterized circuit\n", - "// to visualize the energy landscape\n", - "theta := param.New(\"theta\")\n", - "b := builder.New(\"sweep-demo\", 1)\n", - "b.SymRY(theta.Expr(), 0)\n", - "b.MeasureAll()\n", - "sweepCircuit, _ := b.Build()\n", - "\n", - "ctx := context.Background()\n", - "sw := sweep.Linspace{Key: \"theta\", Start: 0, Stop: 2 * math.Pi, Count: 50}\n", - "results, err := sweep.RunSim(ctx, sweepCircuit, 1000, sw)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Parameter Sweep: RY(theta) on |0>\")\n", - "fmt.Println(\"===================================\")\n", - "fmt.Printf(\"%8s %8s %8s\\n\", \"theta\", \"P(|0>)\", \"P(|1>)\")\n", - "fmt.Println(\"--------------------------\")\n", - "\n", - "for i, res := range results {\n", - "\tif res.Err != nil {\n", - "\t\tcontinue\n", - "\t}\n", - "\tif i%5 == 0 || i == len(results)-1 {\n", - "\t\ttotal := 0\n", - "\t\tfor _, c := range res.Counts {\n", - "\t\t\ttotal += c\n", - "\t\t}\n", - "\t\tp0 := float64(res.Counts[\"0\"]) / float64(total)\n", - "\t\tp1 := float64(res.Counts[\"1\"]) / float64(total)\n", - "\t\tfmt.Printf(\"%8.3f %8.3f %8.3f\\n\", res.Bindings[\"theta\"], p0, p1)\n", - "\t}\n", - "}\n", - "\n", - "fmt.Println(\"\\nRY(theta)|0> = cos(theta/2)|0> + sin(theta/2)|1>\")\n", - "fmt.Println(\"At theta=0: pure |0>. At theta=pi: pure |1>. At theta=2pi: back to |0>.\")" - ] - }, - { - "cell_type": "markdown", - "id": "1qlze9guwt5", - "metadata": {}, - "source": [ - "## Barren Plateaus\n", - "\n", - "A critical practical limitation of variational algorithms is the **barren plateau** phenomenon: as the number of qubits grows, the gradient of the cost function can become exponentially small across most of the parameter space, making optimization nearly impossible.\n", - "\n", - "**Why barren plateaus occur:**\n", - "- **Deep circuits**: More layers means more entanglement, which randomizes the state and flattens the cost landscape\n", - "- **Global observables**: Measuring a global property (like total energy) produces smaller gradients than local measurements\n", - "- **Hardware noise**: Noise can further flatten the landscape by mixing the quantum state toward the maximally mixed state\n", - "\n", - "**How to mitigate them:**\n", - "- Use **shallow ansatze** with few layers (e.g., 1-2 reps of RealAmplitudes)\n", - "- Prefer **local cost functions** that measure individual qubits rather than global observables\n", - "- Use **layerwise training**: optimize one layer at a time, then freeze and add the next\n", - "- Initialize parameters **close to identity** so the circuit starts near a known good state\n", - "- Use **problem-informed ansatze** (like UCCSD for chemistry) rather than generic templates\n", - "\n", - "Barren plateaus are one reason why \"just add more layers\" is not always the right approach — a key difference from classical deep learning where depth generally helps." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "cho2asvrnpp", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Barren Plateau Demonstration\n", - "============================\n", - "\n", - "Gradient variance vs qubit count (EfficientSU2, 2 reps):\n", - "\n", - " 2 qubits (12 params): avg |grad|² = 0.097488\n", - " 3 qubits (18 params): avg |grad|² = 0.091333\n", - " 4 qubits (24 params): avg |grad|² = 0.071685\n", - " 5 qubits (30 params): avg |grad|² = 0.053981\n", - " 6 qubits (36 params): avg |grad|² = 0.041772\n", - "\n", - "As qubit count grows, the gradient magnitude shrinks —\n", - "this is the barren plateau effect.\n" - ] - } - ], - "source": [ - "%%\n", - "// Demonstrate barren plateaus: gradient shrinks with qubit count\n", - "// We compute the cost function gradient for random parameters\n", - "// at different qubit counts and show the variance decreases\n", - "\n", - "fmt.Println(\"Barren Plateau Demonstration\")\n", - "fmt.Println(\"============================\")\n", - "fmt.Println()\n", - "fmt.Println(\"Gradient variance vs qubit count (EfficientSU2, 2 reps):\")\n", - "fmt.Println()\n", - "\n", - "for _, nq := range []int{2, 3, 4, 5, 6} {\n", - " // Create ansatz and Hamiltonian\n", - " a := ansatz.NewEfficientSU2(nq, 2, ansatz.Full)\n", - " ac, _ := a.Circuit()\n", - " paramNames := ir.FreeParameters(ac)\n", - " \n", - " // Simple ZZ Hamiltonian\n", - " terms := []pauli.PauliString{}\n", - " for i := 0; i < nq-1; i++ {\n", - " ops := make(map[int]pauli.Pauli)\n", - " ops[i] = pauli.Z\n", - " ops[i+1] = pauli.Z\n", - " terms = append(terms, pauli.NewPauliString(1, ops, nq))\n", - " }\n", - " ham, _ := pauli.NewPauliSum(terms)\n", - " \n", - " costFn := gradient.CostFunc(ac, ham, paramNames)\n", - " gradFn := gradient.FiniteDifference(costFn, 1e-4)\n", - " \n", - " // Sample gradients at random points\n", - " totalVar := 0.0\n", - " samples := 20\n", - " for s := 0; s < samples; s++ {\n", - " // Random parameters in [0, 2*pi]\n", - " params := make([]float64, len(paramNames))\n", - " for i := range params {\n", - " params[i] = float64(s*17+i*31%100) / 100.0 * 2 * math.Pi // deterministic pseudo-random\n", - " }\n", - " g := gradFn(params)\n", - " for _, gi := range g {\n", - " totalVar += gi * gi\n", - " }\n", - " }\n", - " avgVar := totalVar / float64(samples*len(paramNames))\n", - " fmt.Printf(\" %d qubits (%d params): avg |grad|² = %.6f\\n\", nq, len(paramNames), avgVar)\n", - "}\n", - "\n", - "fmt.Println()\n", - "fmt.Println(\"As qubit count grows, the gradient magnitude shrinks —\")\n", - "fmt.Println(\"this is the barren plateau effect.\")" - ] - }, - { - "cell_type": "markdown", - "id": "a9b0c1d2", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** What happens if you use too few QAOA layers for MaxCut on\n", - "a triangle graph?\n", - "\n", - "**Prediction:** A triangle (K3) has MaxCut = 2. With $p = 1$ layer, QAOA\n", - "has only 2 parameters ($\\gamma_1, \\beta_1$) to work with, which limits\n", - "the quality of the approximation. The **approximation ratio**\n", - "$r = C_{\\text{QAOA}} / C_{\\text{max}}$ should improve as we increase $p$.\n", - "For $p = 1$, the ratio is bounded away from 1; for larger $p$, it should\n", - "approach 1.\n", - "\n", - "Let's sweep $p$ from 1 to 5 and track the approximation ratio." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "b0c1d2e3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "QAOA Layer Sweep on Triangle Graph (MaxCut = 2)\n", - "================================================\n", - " Layers Best Cost Bitstring Approx Ratio Converged\n", - "--------------------------------------------------------------\n", - " 1 -2.0000 011 1.0000 true\n", - " 2 -2.0000 101 1.0000 true\n", - " 3 -2.0000 011 1.0000 true\n", - " 4 -2.0000 100 1.0000 true\n", - " 5 -2.0000 100 1.0000 true\n", - "\n", - "More layers give the optimizer more degrees of freedom.\n", - "The approximation ratio should approach 1.0 as p increases.\n", - "However, more layers also mean deeper circuits and harder optimization.\n" - ] - } - ], - "source": [ - "%%\n", - "// QAOA with increasing layers on a triangle graph\n", - "edges := [][2]int{{0, 1}, {1, 2}, {0, 2}}\n", - "costH, _ := qaoa.MaxCutHamiltonian(edges, 3)\n", - "maxCut := 2.0 // Known optimal for K3\n", - "\n", - "ctx := context.Background()\n", - "\n", - "fmt.Println(\"QAOA Layer Sweep on Triangle Graph (MaxCut = 2)\")\n", - "fmt.Println(\"================================================\")\n", - "fmt.Printf(\"%8s %12s %12s %14s %10s\\n\",\n", - "\t\"Layers\", \"Best Cost\", \"Bitstring\", \"Approx Ratio\", \"Converged\")\n", - "fmt.Println(\"--------------------------------------------------------------\")\n", - "\n", - "for p := 1; p <= 5; p++ {\n", - "\tres, err := qaoa.Run(ctx, qaoa.Config{\n", - "\t\tCostHamiltonian: costH,\n", - "\t\tLayers: p,\n", - "\t\tOptimizer: &optim.NelderMead{},\n", - "\t\tShots: 2000,\n", - "\t})\n", - "\tif err != nil {\n", - "\t\tfmt.Printf(\"%8d error: %v\\n\", p, err)\n", - "\t\tcontinue\n", - "\t}\n", - "\t// MaxCut cost is negated in the Hamiltonian, so negate BestCost\n", - "\tcutValue := -res.BestCost\n", - "\tratio := cutValue / maxCut\n", - "\tfmt.Printf(\"%8d %12.4f %12s %14.4f %10v\\n\",\n", - "\t\tp, res.BestCost, res.BestBitstring, ratio, res.Converged)\n", - "}\n", - "\n", - "fmt.Println(\"\\nMore layers give the optimizer more degrees of freedom.\")\n", - "fmt.Println(\"The approximation ratio should approach 1.0 as p increases.\")\n", - "fmt.Println(\"However, more layers also mean deeper circuits and harder optimization.\")" - ] - }, - { - "cell_type": "markdown", - "id": "043xgbwios5h", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. Why might adding more ansatz layers hurt rather than help optimization?\n", - "2. What is the key difference between gradient-free (Nelder-Mead) and gradient-based (Adam) optimizers for VQE?\n", - "3. In QAOA, why does increasing the number of layers p improve the approximation ratio?" - ] - }, - { - "cell_type": "markdown", - "id": "c1d2e3f4", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## Exercises\n", - "\n", - "### Exercise 1 -- Compare Ansatze on the H$_2$ Hamiltonian\n", - "\n", - "Run VQE on the H$_2$ Hamiltonian using three different ansatze\n", - "(RealAmplitudes, EfficientSU2, StronglyEntanglingLayers) and compare\n", - "the resulting ground state energies. Which ansatz gets closest to the\n", - "exact value, and at what computational cost (parameters, iterations)?" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "d2e3f4a5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above, fill in the ???, and run!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: Compare Ansatze on the H2 Hamiltonian\n", - "//\n", - "// Goal: Run VQE with three different ansatze and compare ground state energies.\n", - "//\n", - "// TODO: Build the H2 Hamiltonian (same as the VQE section above)\n", - "// TODO: Create three ansatze: RealAmplitudes, EfficientSU2, StronglyEntanglingLayers\n", - "// TODO: Run VQE with each and print a comparison table\n", - "//\n", - "// Skeleton:\n", - "\n", - "// ctx := context.Background()\n", - "\n", - "// Step 1: Build the H2 Hamiltonian\n", - "// xx, _ := pauli.Parse(\"XX\")\n", - "// zz, _ := pauli.Parse(\"ZZ\")\n", - "// zi, _ := pauli.Parse(\"ZI\")\n", - "// iz, _ := pauli.Parse(\"IZ\")\n", - "// ii, _ := pauli.Parse(\"II\")\n", - "// h2, _ := pauli.NewPauliSum([]pauli.PauliString{\n", - "// ii.Scale(-1.0523),\n", - "// iz.Scale(0.3979),\n", - "// zi.Scale(-0.3979),\n", - "// zz.Scale(-0.0112),\n", - "// xx.Scale(0.1809),\n", - "// })\n", - "\n", - "// Step 2: Define the ansatze to compare\n", - "// type ansatzEntry struct {\n", - "// name string\n", - "// ansatz ansatz.Ansatz\n", - "// }\n", - "// ansatze := []ansatzEntry{\n", - "// {\"RealAmplitudes\", ansatz.NewRealAmplitudes(2, 2, ansatz.Linear)},\n", - "// {\"EfficientSU2\", ansatz.NewEfficientSU2(???, ???, ???)},\n", - "// {\"StronglyEntangling\", ansatz.NewStronglyEntanglingLayers(???, ???)},\n", - "// }\n", - "\n", - "// Step 3: Loop over ansatze, run VQE, and print results\n", - "// fmt.Printf(\"%-22s %6s %12s %6s %10s\\n\",\n", - "// \"Ansatz\", \"Params\", \"Energy\", \"Iters\", \"Error\")\n", - "// for _, entry := range ansatze {\n", - "// res, err := vqe.Run(ctx, vqe.Config{\n", - "// Hamiltonian: ???,\n", - "// Ansatz: ???,\n", - "// Optimizer: ???,\n", - "// })\n", - "// // Print: entry.name, NumParams(), res.Energy, res.NumIters, error vs -1.137\n", - "// }\n", - "\n", - "_ = context.Background()\n", - "fmt.Println(\"TODO: Uncomment the code above, fill in the ???, and run!\")" - ] - }, - { - "cell_type": "markdown", - "id": "e3f4a5b6", - "metadata": {}, - "source": [ - "### Exercise 2 -- QAOA MaxCut on a 4-Node Graph\n", - "\n", - "Run QAOA on a 4-node graph (a square with one diagonal):\n", - "edges (0,1), (1,2), (2,3), (3,0), (0,2). The maximum cut for this graph\n", - "is 4 (partition {0,2} vs {1,3}). Try different numbers of QAOA layers\n", - "and see how the approximation quality improves." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "f4a5b6c7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above, fill in the ???, and run!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: QAOA MaxCut on a 4-Node Graph\n", - "//\n", - "// Goal: Run QAOA on a square-with-diagonal graph and observe how\n", - "// increasing the number of layers improves the approximation ratio.\n", - "//\n", - "// Graph: edges (0,1), (1,2), (2,3), (3,0), (0,2)\n", - "// Optimal MaxCut = 4 (partition {0,2} vs {1,3})\n", - "//\n", - "// TODO: Build the cost Hamiltonian for MaxCut\n", - "// TODO: Sweep QAOA layers from 1 to 4 and print results\n", - "//\n", - "// Skeleton:\n", - "\n", - "_ = context.Background()\n", - "\n", - "// Step 1: Define edges and build the MaxCut Hamiltonian\n", - "// edges := [][2]int{{0, 1}, {1, 2}, {2, 3}, {3, 0}, {0, 2}}\n", - "// costH, err := qaoa.MaxCutHamiltonian(???, ???)\n", - "// if err != nil {\n", - "// panic(err)\n", - "// }\n", - "// maxCut := 4.0\n", - "\n", - "// Step 2: Sweep over layer counts\n", - "// fmt.Printf(\"%8s %12s %12s %14s\\n\",\n", - "// \"Layers\", \"Best Cost\", \"Bitstring\", \"Approx Ratio\")\n", - "//\n", - "// for p := 1; p <= 4; p++ {\n", - "// res, err := qaoa.Run(ctx, qaoa.Config{\n", - "// CostHamiltonian: ???,\n", - "// Layers: ???,\n", - "// Optimizer: ???,\n", - "// Shots: 2000,\n", - "// })\n", - "// if err != nil {\n", - "// fmt.Printf(\"%8d error: %v\\n\", p, err)\n", - "// continue\n", - "// }\n", - "// cutValue := -res.BestCost\n", - "// ratio := cutValue / maxCut\n", - "// fmt.Printf(\"%8d %12.4f %12s %14.4f\\n\",\n", - "// p, res.BestCost, res.BestBitstring, ratio)\n", - "// }\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above, fill in the ???, and run!\")" - ] - }, - { - "cell_type": "markdown", - "id": "a5b6c7d8", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "\n", - "1. **Variational algorithms** use shallow parameterized circuits optimized\n", - " by classical computers. They are the leading approach for near-term\n", - " quantum hardware because they tolerate limited qubit counts and gate\n", - " fidelities.\n", - "\n", - "2. **Parameterized circuits** use symbolic parameters (`param.New`) that\n", - " are bound to concrete values via `ir.Bind`. This separation of\n", - " structure and values is the foundation of all variational methods.\n", - "\n", - "3. **Ansatz design** is critical. RealAmplitudes, EfficientSU2, and\n", - " StronglyEntanglingLayers offer increasing expressibility at the cost\n", - " of more parameters. The right choice depends on the problem.\n", - "\n", - "4. **VQE** finds ground state energies by minimizing\n", - " $\\langle\\psi(\\theta)|H|\\psi(\\theta)\\rangle$. The variational principle\n", - " guarantees the result is an upper bound on $E_0$.\n", - "\n", - "5. **QAOA** solves combinatorial optimization problems by alternating\n", - " cost and mixer layers. More layers ($p$) improve the approximation\n", - " but increase circuit depth.\n", - "\n", - "6. **Optimizer choice matters**. Nelder-Mead and SPSA are gradient-free;\n", - " Adam and L-BFGS use gradients for faster convergence. The\n", - " parameter-shift rule gives exact quantum gradients.\n", - "\n", - "7. **Energy landscapes** can have local minima, barren plateaus, and\n", - " symmetries that make optimization challenging. Understanding the\n", - " landscape (via parameter sweeps) helps choose initial parameters\n", - " and optimizer settings.\n", - "\n", - "8. **There are no guarantees**: variational algorithms may converge to\n", - " local minima or barren plateaus rather than the global optimum.\n", - " Multiple random initializations and careful ansatz design mitigate\n", - " this, but do not eliminate it.\n", - "\n", - "---\n", - "\n", - "**Next up:** Notebook 14, where we will explore error mitigation\n", - "techniques for improving the accuracy of noisy quantum computations." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/14-quantum-machine-learning.ipynb b/notebooks/14-quantum-machine-learning.ipynb deleted file mode 100644 index aa09b3a..0000000 --- a/notebooks/14-quantum-machine-learning.ipynb +++ /dev/null @@ -1,1034 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a1b2c3d4", - "metadata": {}, - "source": [ - "# Notebook 14 -- Quantum Machine Learning\n", - "\n", - "**Prerequisites:** Notebook 13. Familiarity with variational circuits and optimizers.\n", - "\n", - "Classical machine learning relies on kernel methods, feature spaces, and\n", - "gradient-based optimization. Quantum computing offers a new twist on all\n", - "three: a quantum computer can prepare exponentially large feature spaces,\n", - "compute kernel entries via quantum interference, and train parameterized\n", - "circuits with classical optimizers in a hybrid loop.\n", - "\n", - "**Quantum machine learning (QML)** encodes classical data into quantum\n", - "states, processes it through parameterized quantum circuits, and extracts\n", - "predictions via measurement. The hope is that quantum feature maps can\n", - "capture correlations in data that are hard for classical models to learn.\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Describe** quantum feature maps and how they encode classical data into quantum states.\n", - "2. **Implement** a variational quantum classifier and evaluate its accuracy.\n", - "3. **Compute** and interpret a quantum kernel matrix.\n", - "4. **Compare** different feature map strategies.\n", - "\n", - "In this notebook we will:\n", - "\n", - "1. Explore **quantum feature maps** -- circuits that encode classical data\n", - " into quantum states (angle embedding, amplitude embedding, Z and ZZ\n", - " feature maps).\n", - "2. Build and train a **Variational Quantum Classifier (VQC)** -- a hybrid\n", - " classical-quantum model that classifies data using a parameterized\n", - " quantum circuit.\n", - "3. Compute **quantum kernel matrices** -- inner products between quantum\n", - " feature states that can be fed into classical kernel methods.\n", - "4. Predict and verify classification on unseen test data.\n", - "\n", - "### Misconception: Quantum ML always outperforms classical ML\n", - "\n", - "Quantum advantage in ML is not guaranteed. For many datasets, classical\n", - "models match or exceed quantum models. The potential advantage comes from\n", - "quantum feature maps that create classically hard-to-simulate kernel\n", - "functions. Whether a given dataset benefits from quantum encoding is an\n", - "active research question." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "b2c3d4e5", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"context\"\n", - "\t\"fmt\"\n", - "\t\"math\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/algorithm/ansatz\"\n", - "\t\"github.com/splch/goqu/algorithm/optim\"\n", - "\t\"github.com/splch/goqu/algorithm/vqc\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/circuit/ir\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "c3d4e5f6", - "metadata": {}, - "source": [ - "## Quantum Feature Maps\n", - "\n", - "A **feature map** is a circuit that encodes a classical data vector\n", - "$\\vec{x} \\in \\mathbb{R}^n$ into a quantum state $|\\phi(\\vec{x})\\rangle$.\n", - "Different encoding strategies create different quantum feature spaces:\n", - "\n", - "| Feature Map | Encoding | Expressivity |\n", - "|:---|:---|:---|\n", - "| **Angle Embedding** | Each feature $x_i$ becomes a rotation angle on qubit $i$ | Linear -- each feature maps to one rotation |\n", - "| **Z Feature Map** | H + RZ($x_i$) per qubit, repeated $d$ times | Product state -- no entanglement |\n", - "| **ZZ Feature Map** | Like Z, plus RZZ($x_i \\cdot x_j$) entangling layers | Captures pairwise feature interactions |\n", - "| **Amplitude Embedding** | Feature vector encoded as state amplitudes | Exponentially compact -- $2^n$ features in $n$ qubits |\n", - "\n", - "The choice of feature map determines what patterns the classifier can\n", - "learn. More expressive feature maps can capture more complex decision\n", - "boundaries, but may also be harder to train.\n", - "\n", - "Let's build circuits with each feature map and inspect their structure." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "d4e5f6a7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== Angle Embedding (RY) ===\n", - "Each feature becomes an RY rotation on the corresponding qubit.\n", - "=== Z Feature Map (depth=2) ===\n", - "H + RZ(x_i) per qubit, repeated twice. No entanglement.\n", - "=== ZZ Feature Map (depth=2) ===\n", - "Like Z, plus RZZ(x_i * x_j) entangling gates between adjacent qubits.\n", - "The ZZ feature map creates entanglement between qubits, encoding\n", - "pairwise feature interactions that the Z map cannot capture.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "RY(0.3)\n", - "RY(0.7)\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "RZ(0.3)\n", - "H\n", - "RZ(0.7)\n", - "H\n", - "RZ(0.3)\n", - "H\n", - "RZ(0.7)\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "RZ(0.3)\n", - "H\n", - "RZ(0.7)\n", - "\n", - "RZZ(0.21)\n", - "RZZ(0.21)\n", - "H\n", - "RZ(0.3)\n", - "H\n", - "RZ(0.7)\n", - "\n", - "RZZ(0.21)\n", - "RZZ(0.21)\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Demonstrate different feature maps with sample data\n", - "sampleData := []float64{0.3, 0.7}\n", - "qubits := []int{0, 1}\n", - "\n", - "// AngleEmbedding: encodes features as rotation angles\n", - "angleFM := vqc.AngleEmbedding(vqc.RotRY)\n", - "\n", - "ab := builder.New(\"angle-ry\", 2)\n", - "angleFM(ab, sampleData, qubits)\n", - "angleCirc, _ := ab.Build()\n", - "\n", - "fmt.Println(\"=== Angle Embedding (RY) ===\")\n", - "fmt.Println(\"Each feature becomes an RY rotation on the corresponding qubit.\")\n", - "gonbui.DisplayHTML(draw.SVG(angleCirc))\n", - "\n", - "// ZFeatureMap: H + RZ(x) per qubit, repeated depth times\n", - "zFM := vqc.ZFeatureMap(2)\n", - "\n", - "zb := builder.New(\"z-feature\", 2)\n", - "zFM(zb, sampleData, qubits)\n", - "zCirc, _ := zb.Build()\n", - "\n", - "fmt.Println(\"=== Z Feature Map (depth=2) ===\")\n", - "fmt.Println(\"H + RZ(x_i) per qubit, repeated twice. No entanglement.\")\n", - "gonbui.DisplayHTML(draw.SVG(zCirc))\n", - "\n", - "// ZZFeatureMap: like Z but with RZZ(x_i * x_j) entangling layers\n", - "zzFM := vqc.ZZFeatureMap(2)\n", - "\n", - "zzb := builder.New(\"zz-feature\", 2)\n", - "zzFM(zzb, sampleData, qubits)\n", - "zzCirc, _ := zzb.Build()\n", - "\n", - "fmt.Println(\"=== ZZ Feature Map (depth=2) ===\")\n", - "fmt.Println(\"Like Z, plus RZZ(x_i * x_j) entangling gates between adjacent qubits.\")\n", - "gonbui.DisplayHTML(draw.SVG(zzCirc))\n", - "\n", - "fmt.Println(\"The ZZ feature map creates entanglement between qubits, encoding\")\n", - "fmt.Println(\"pairwise feature interactions that the Z map cannot capture.\")" - ] - }, - { - "cell_type": "markdown", - "id": "e5f6a7b8", - "metadata": {}, - "source": [ - "## Amplitude Embedding\n", - "\n", - "While angle embedding uses one qubit per feature, **amplitude embedding**\n", - "encodes an entire feature vector into the amplitudes of a quantum state.\n", - "An $n$-qubit state has $2^n$ amplitudes, so $n$ qubits can encode up to\n", - "$2^n$ classical features -- an exponential compression.\n", - "\n", - "The feature vector $\\vec{x}$ is normalized to unit L2 norm and mapped to:\n", - "\n", - "$$|\\phi(\\vec{x})\\rangle = \\sum_{i=0}^{2^n - 1} \\frac{x_i}{\\|\\vec{x}\\|} |i\\rangle$$\n", - "\n", - "This is powerful for high-dimensional data, but the state preparation\n", - "circuit can be deep. Let's encode a 4-dimensional feature vector into\n", - "2 qubits." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "f6a7b8c9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== Amplitude Embedding ===\n", - "4D feature vector [1, 2, 3, 4] encoded into 2 qubits.\n", - "Expected amplitudes (normalized):\n", - " |00> = 0.1826\n", - " |01> = 0.3651\n", - " |10> = 0.5477\n", - " |11> = 0.7303\n", - "\n", - "Amplitude embedding encodes 4 features into 2 qubits.\n", - "With 10 qubits, you could encode up to 1024 features.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "\n", - "Prep\n", - "Prep\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// AmplitudeEmbedding: encode a 4D feature vector into 2 qubits\n", - "ampFM := vqc.AmplitudeEmbedding()\n", - "\n", - "features4D := []float64{1.0, 2.0, 3.0, 4.0}\n", - "qubits := []int{0, 1}\n", - "\n", - "ampb := builder.New(\"amp-embed\", 2)\n", - "ampFM(ampb, features4D, qubits)\n", - "ampCirc, _ := ampb.Build()\n", - "\n", - "fmt.Println(\"=== Amplitude Embedding ===\")\n", - "fmt.Println(\"4D feature vector [1, 2, 3, 4] encoded into 2 qubits.\")\n", - "gonbui.DisplayHTML(draw.SVG(ampCirc))\n", - "\n", - "// Verify the amplitudes match the normalized feature vector\n", - "norm := math.Sqrt(1*1 + 2*2 + 3*3 + 4*4)\n", - "fmt.Println(\"Expected amplitudes (normalized):\")\n", - "for i, f := range features4D {\n", - "\tfmt.Printf(\" |%02b> = %.4f\\n\", i, f/norm)\n", - "}\n", - "\n", - "fmt.Printf(\"\\nAmplitude embedding encodes %d features into %d qubits.\\n\",\n", - "\tlen(features4D), 2)\n", - "fmt.Println(\"With 10 qubits, you could encode up to 1024 features.\")" - ] - }, - { - "cell_type": "markdown", - "id": "a7b8c9d0", - "metadata": {}, - "source": [ - "## Variational Quantum Classifier (VQC)\n", - "\n", - "A **Variational Quantum Classifier** is a hybrid classical-quantum model.\n", - "The quantum circuit has two parts:\n", - "\n", - "1. **Feature map**: encodes the input data $\\vec{x}$ into a quantum state.\n", - "2. **Variational ansatz**: a parameterized circuit $U(\\vec{\\theta})$ whose\n", - " parameters $\\vec{\\theta}$ are optimized to minimize classification loss.\n", - "\n", - "The full circuit is $U(\\vec{\\theta}) \\cdot V(\\vec{x}) |0\\rangle$, and the\n", - "prediction is based on measuring qubit 0: $P(\\text{class 1}) = 1 - |\\langle 0|\\psi\\rangle|^2$.\n", - "\n", - "Training uses a classical optimizer (here Nelder-Mead) to find the\n", - "parameters $\\vec{\\theta}^*$ that minimize the MSE loss over the training set.\n", - "\n", - "```\n", - "Classical data --> Feature Map --> Ansatz(theta) --> Measure --> Prediction\n", - " x V(x)|0> U(theta) P(class)\n", - " ^\n", - " |\n", - " Classical optimizer updates theta\n", - "```\n", - "\n", - "Let's create a simple 2D classification problem and train a VQC." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "b8c9d0e1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Training Data\n", - "=============\n", - "Point Label\n", - "--------------------\n", - "(0.1, 0.9) 0 (above y=x)\n", - "(0.2, 0.8) 0 (above y=x)\n", - "(0.3, 0.7) 0 (above y=x)\n", - "(0.8, 0.2) 1 (below y=x)\n", - "(0.9, 0.1) 1 (below y=x)\n", - "(0.7, 0.3) 1 (below y=x)\n", - "\n", - "Ansatz: RealAmplitudes(2 qubits, 2 reps, Linear entanglement)\n", - "Parameters: 6\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "RY\n", - "RY\n", - "\n", - "\n", - "\n", - "RY\n", - "RY\n", - "\n", - "\n", - "\n", - "RY\n", - "RY\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Simple classification: points above the y=x line (class 0) vs below (class 1)\n", - "trainX := [][]float64{\n", - "\t{0.1, 0.9}, {0.2, 0.8}, {0.3, 0.7}, // class 0: above y=x\n", - "\t{0.8, 0.2}, {0.9, 0.1}, {0.7, 0.3}, // class 1: below y=x\n", - "}\n", - "trainY := []int{0, 0, 0, 1, 1, 1}\n", - "\n", - "fmt.Println(\"Training Data\")\n", - "fmt.Println(\"=============\")\n", - "fmt.Printf(\"%-12s %s\\n\", \"Point\", \"Label\")\n", - "fmt.Println(\"--------------------\")\n", - "for i, x := range trainX {\n", - "\tside := \"above y=x\"\n", - "\tif trainY[i] == 1 {\n", - "\t\tside = \"below y=x\"\n", - "\t}\n", - "\tfmt.Printf(\"(%.1f, %.1f) %d (%s)\\n\", x[0], x[1], trainY[i], side)\n", - "}\n", - "\n", - "// Show the ansatz structure\n", - "ans := ansatz.NewRealAmplitudes(2, 2, ansatz.Linear)\n", - "ansCirc, _ := ans.Circuit()\n", - "fmt.Printf(\"\\nAnsatz: RealAmplitudes(2 qubits, 2 reps, Linear entanglement)\\n\")\n", - "fmt.Printf(\"Parameters: %d\\n\", ans.NumParams())\n", - "gonbui.DisplayHTML(draw.SVG(ansCirc))" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "c9d0e1f2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Training accuracy: 100.00%\n", - "Converged: true, Iterations: 143\n", - "Optimal parameters: [-0.1501, -0.0547, 0.4158, -1.3227, 0.5294, -0.3601]\n" - ] - } - ], - "source": [ - "%%\n", - "// Train the VQC\n", - "ctx := context.Background()\n", - "\n", - "trainX := [][]float64{\n", - "\t{0.1, 0.9}, {0.2, 0.8}, {0.3, 0.7},\n", - "\t{0.8, 0.2}, {0.9, 0.1}, {0.7, 0.3},\n", - "}\n", - "trainY := []int{0, 0, 0, 1, 1, 1}\n", - "\n", - "cfg := vqc.Config{\n", - "\tNumQubits: 2,\n", - "\tFeatureMap: vqc.AngleEmbedding(vqc.RotRY),\n", - "\tAnsatz: ansatz.NewRealAmplitudes(2, 2, ansatz.Linear),\n", - "\tOptimizer: &optim.NelderMead{},\n", - "\tTrainX: trainX,\n", - "\tTrainY: trainY,\n", - "}\n", - "\n", - "result, err := vqc.Run(ctx, cfg)\n", - "if err != nil {\n", - "\tfmt.Println(\"Error:\", err)\n", - "} else {\n", - "\tfmt.Printf(\"Training accuracy: %.2f%%\\n\", result.TrainAccuracy*100)\n", - "\tfmt.Printf(\"Converged: %v, Iterations: %d\\n\", result.Converged, result.NumIters)\n", - "\tfmt.Printf(\"Optimal parameters: [\")\n", - "\tfor i, p := range result.OptimalParams {\n", - "\t\tif i > 0 {\n", - "\t\t\tfmt.Printf(\", \")\n", - "\t\t}\n", - "\t\tfmt.Printf(\"%.4f\", p)\n", - "\t}\n", - "\tfmt.Println(\"]\")\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "d0e1f2a3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Test Predictions\n", - "================\n", - " (0.1, 0.8) -> class 0 (expected: above y=x (class 0))\n", - " (0.8, 0.1) -> class 1 (expected: below y=x (class 1))\n", - " (0.5, 0.5) -> class 1 (expected: above y=x (class 0))\n", - "\n", - "The midpoint (0.5, 0.5) lies exactly on the decision boundary.\n", - "Its classification depends on the learned parameters.\n" - ] - } - ], - "source": [ - "%%\n", - "// Make predictions on test data\n", - "ctx := context.Background()\n", - "\n", - "trainX := [][]float64{\n", - "\t{0.1, 0.9}, {0.2, 0.8}, {0.3, 0.7},\n", - "\t{0.8, 0.2}, {0.9, 0.1}, {0.7, 0.3},\n", - "}\n", - "trainY := []int{0, 0, 0, 1, 1, 1}\n", - "\n", - "cfg := vqc.Config{\n", - "\tNumQubits: 2,\n", - "\tFeatureMap: vqc.AngleEmbedding(vqc.RotRY),\n", - "\tAnsatz: ansatz.NewRealAmplitudes(2, 2, ansatz.Linear),\n", - "\tOptimizer: &optim.NelderMead{},\n", - "\tTrainX: trainX,\n", - "\tTrainY: trainY,\n", - "}\n", - "\n", - "result, err := vqc.Run(ctx, cfg)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "// Predict on unseen test points\n", - "testX := [][]float64{{0.1, 0.8}, {0.8, 0.1}, {0.5, 0.5}}\n", - "preds, err := vqc.Predict(cfg, result.OptimalParams, testX)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Test Predictions\")\n", - "fmt.Println(\"================\")\n", - "for i, x := range testX {\n", - "\texpected := \"above y=x (class 0)\"\n", - "\tif x[0] > x[1] {\n", - "\t\texpected = \"below y=x (class 1)\"\n", - "\t}\n", - "\tfmt.Printf(\" (%.1f, %.1f) -> class %d (expected: %s)\\n\", x[0], x[1], preds[i], expected)\n", - "}\n", - "\n", - "fmt.Println(\"\\nThe midpoint (0.5, 0.5) lies exactly on the decision boundary.\")\n", - "fmt.Println(\"Its classification depends on the learned parameters.\")" - ] - }, - { - "cell_type": "markdown", - "id": "e1f2a3b4", - "metadata": {}, - "source": [ - "## Quantum Kernels\n", - "\n", - "A **quantum kernel** measures the similarity between two data points in\n", - "quantum feature space. Given a feature map $V$, the kernel entry is:\n", - "\n", - "$$K(\\vec{x}_i, \\vec{x}_j) = |\\langle 0 | V^\\dagger(\\vec{x}_j) \\cdot V(\\vec{x}_i) | 0 \\rangle|^2$$\n", - "\n", - "This is the **fidelity** between the two feature states -- it equals 1\n", - "when the states are identical and approaches 0 when they are orthogonal.\n", - "\n", - "The kernel matrix $K$ can be computed on a quantum computer and then used\n", - "with classical kernel methods (SVM, kernel ridge regression). This separates\n", - "the quantum advantage (computing kernels in exponential feature spaces) from\n", - "the classical training (optimizing a convex problem with the kernel matrix).\n", - "\n", - "Key properties:\n", - "- $K(\\vec{x}, \\vec{x}) = 1$ always (self-similarity).\n", - "- $K$ is symmetric and positive semi-definite.\n", - "- Entangling feature maps (ZZ) create kernels that are classically hard\n", - " to compute, which is where quantum advantage may arise." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "f2a3b4c5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Quantum Kernel Matrix (ZZ Feature Map, depth=2)\n", - "================================================\n", - "\n", - "Labels: [0, 0, 0, 1, 1, 1]\n", - "\n", - " x0 x1 x2 x3 x4 x5 \n", - "x0 1.000 0.993 0.974 0.734 0.680 0.792\n", - "x1 0.993 1.000 0.993 0.788 0.734 0.843\n", - "x2 0.974 0.993 1.000 0.843 0.792 0.894\n", - "x3 0.734 0.788 0.843 1.000 0.993 0.993\n", - "x4 0.680 0.734 0.792 0.993 1.000 0.974\n", - "x5 0.792 0.843 0.894 0.993 0.974 1.000\n", - "\n", - "Diagonal entries are 1.000 (self-similarity).\n", - "Same-class pairs should have higher kernel values than cross-class pairs.\n" - ] - } - ], - "source": [ - "%%\n", - "// Compute the quantum kernel matrix using the ZZ feature map\n", - "ctx := context.Background()\n", - "\n", - "trainX := [][]float64{\n", - "\t{0.1, 0.9}, {0.2, 0.8}, {0.3, 0.7},\n", - "\t{0.8, 0.2}, {0.9, 0.1}, {0.7, 0.3},\n", - "}\n", - "\n", - "kcfg := vqc.KernelConfig{\n", - "\tNumQubits: 2,\n", - "\tFeatureMap: vqc.ZZFeatureMap(2),\n", - "}\n", - "\n", - "K, err := vqc.KernelMatrix(ctx, kcfg, trainX)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Quantum Kernel Matrix (ZZ Feature Map, depth=2)\")\n", - "fmt.Println(\"================================================\")\n", - "fmt.Println(\"\\nLabels: [0, 0, 0, 1, 1, 1]\")\n", - "fmt.Println()\n", - "\n", - "// Print header\n", - "fmt.Printf(\"%6s\", \"\")\n", - "for i := range K {\n", - "\tfmt.Printf(\" x%-4d\", i)\n", - "}\n", - "fmt.Println()\n", - "\n", - "for i, row := range K {\n", - "\tfmt.Printf(\"x%-4d\", i)\n", - "\tfor _, v := range row {\n", - "\t\tfmt.Printf(\" %.3f\", v)\n", - "\t}\n", - "\tfmt.Println()\n", - "}\n", - "\n", - "fmt.Println(\"\\nDiagonal entries are 1.000 (self-similarity).\")\n", - "fmt.Println(\"Same-class pairs should have higher kernel values than cross-class pairs.\")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "a3b4c5d6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "K(x0, x1) = 0.9932 (both class 0, nearby points)\n", - "K(x0, x5) = 0.7916 (class 0 vs class 1, distant points)\n", - "K(x3, x4) = 0.9932 (both class 1, nearby points)\n", - "K(x2, x3) = 0.8432 (class 0 vs class 1, distant points)\n", - "\n", - "A good feature map produces high kernel values for same-class pairs\n", - "and low values for cross-class pairs, enabling linear separation in\n", - "the quantum feature space.\n" - ] - } - ], - "source": [ - "%%\n", - "// Compute individual kernel entries to examine specific pair similarities\n", - "trainX := [][]float64{\n", - "\t{0.1, 0.9}, {0.2, 0.8}, {0.3, 0.7},\n", - "\t{0.8, 0.2}, {0.9, 0.1}, {0.7, 0.3},\n", - "}\n", - "\n", - "kcfg := vqc.KernelConfig{\n", - "\tNumQubits: 2,\n", - "\tFeatureMap: vqc.ZZFeatureMap(2),\n", - "}\n", - "\n", - "// Same class: x0 (class 0) vs x1 (class 0)\n", - "k01, _ := vqc.KernelEntry(kcfg, trainX[0], trainX[1])\n", - "fmt.Printf(\"K(x0, x1) = %.4f (both class 0, nearby points)\\n\", k01)\n", - "\n", - "// Cross class: x0 (class 0) vs x5 (class 1)\n", - "k05, _ := vqc.KernelEntry(kcfg, trainX[0], trainX[5])\n", - "fmt.Printf(\"K(x0, x5) = %.4f (class 0 vs class 1, distant points)\\n\", k05)\n", - "\n", - "// Same class: x3 (class 1) vs x4 (class 1)\n", - "k34, _ := vqc.KernelEntry(kcfg, trainX[3], trainX[4])\n", - "fmt.Printf(\"K(x3, x4) = %.4f (both class 1, nearby points)\\n\", k34)\n", - "\n", - "// Cross class: x2 (class 0) vs x3 (class 1)\n", - "k23, _ := vqc.KernelEntry(kcfg, trainX[2], trainX[3])\n", - "fmt.Printf(\"K(x2, x3) = %.4f (class 0 vs class 1, distant points)\\n\", k23)\n", - "\n", - "fmt.Println(\"\\nA good feature map produces high kernel values for same-class pairs\")\n", - "fmt.Println(\"and low values for cross-class pairs, enabling linear separation in\")\n", - "fmt.Println(\"the quantum feature space.\")" - ] - }, - { - "cell_type": "markdown", - "id": "b4c5d6e7", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** Will the VQC correctly classify the midpoint $(0.5, 0.5)$?\n", - "\n", - "**Prediction:** The point $(0.5, 0.5)$ lies exactly on the $y = x$\n", - "decision boundary. Our training data has class 0 points above the line\n", - "and class 1 points below it. Since $(0.5, 0.5)$ is exactly on the\n", - "boundary, the classifier's prediction depends on the learned decision\n", - "surface. With a linear separation model, it could go either way.\n", - "\n", - "For the clearly separated test points $(0.1, 0.8)$ and $(0.8, 0.1)$,\n", - "the VQC should classify them correctly as class 0 and class 1\n", - "respectively, since they are well within their class regions.\n", - "\n", - "Let's verify." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "c5d6e7f8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Training accuracy: 100%\n", - "\n", - "Verification:\n", - " (0.1, 0.8) -> class 0 [predicted: class 0, above y=x]\n", - " (0.8, 0.1) -> class 1 [predicted: class 1, below y=x]\n", - " (0.5, 0.5) -> class 1 [on the boundary -- could go either way]\n", - "\n", - "Clear points classified correctly: true\n", - "The boundary point (0.5, 0.5) classification depends on the learned parameters.\n" - ] - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "trainX := [][]float64{\n", - "\t{0.1, 0.9}, {0.2, 0.8}, {0.3, 0.7},\n", - "\t{0.8, 0.2}, {0.9, 0.1}, {0.7, 0.3},\n", - "}\n", - "trainY := []int{0, 0, 0, 1, 1, 1}\n", - "\n", - "cfg := vqc.Config{\n", - "\tNumQubits: 2,\n", - "\tFeatureMap: vqc.AngleEmbedding(vqc.RotRY),\n", - "\tAnsatz: ansatz.NewRealAmplitudes(2, 2, ansatz.Linear),\n", - "\tOptimizer: &optim.NelderMead{},\n", - "\tTrainX: trainX,\n", - "\tTrainY: trainY,\n", - "}\n", - "\n", - "result, err := vqc.Run(ctx, cfg)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"Training accuracy: %.0f%%\\n\\n\", result.TrainAccuracy*100)\n", - "\n", - "// Test the specific predictions\n", - "testPoints := [][]float64{\n", - "\t{0.1, 0.8}, // clearly above y=x -> class 0\n", - "\t{0.8, 0.1}, // clearly below y=x -> class 1\n", - "\t{0.5, 0.5}, // exactly on boundary -> ambiguous\n", - "}\n", - "\n", - "preds, _ := vqc.Predict(cfg, result.OptimalParams, testPoints)\n", - "\n", - "fmt.Println(\"Verification:\")\n", - "fmt.Printf(\" (0.1, 0.8) -> class %d [predicted: class 0, above y=x]\\n\", preds[0])\n", - "fmt.Printf(\" (0.8, 0.1) -> class %d [predicted: class 1, below y=x]\\n\", preds[1])\n", - "fmt.Printf(\" (0.5, 0.5) -> class %d [on the boundary -- could go either way]\\n\", preds[2])\n", - "\n", - "clearCorrect := (preds[0] == 0) && (preds[1] == 1)\n", - "fmt.Printf(\"\\nClear points classified correctly: %v\\n\", clearCorrect)\n", - "fmt.Println(\"The boundary point (0.5, 0.5) classification depends on the learned parameters.\")" - ] - }, - { - "cell_type": "markdown", - "id": "6c4hvi7e7uo", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. Why might a quantum kernel outperform a classical kernel on some datasets but not others?\n", - "2. What is the difference between angle embedding and amplitude embedding in terms of qubit efficiency?\n", - "3. Why does a quantum kernel matrix always have 1.0 on its diagonal?" - ] - }, - { - "cell_type": "markdown", - "id": "d6e7f8a9", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## Exercises\n", - "\n", - "### Exercise 1 -- Compare Feature Maps\n", - "\n", - "Train the VQC with three different feature maps (angle embedding, Z feature\n", - "map, and ZZ feature map) on the same dataset. Compare their training\n", - "accuracy and convergence. Which feature map works best for this linearly\n", - "separable dataset?" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "e7f8a9b0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above, fill in the ???, and run!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: Compare Feature Maps\n", - "//\n", - "// Goal: Train the VQC with three different feature maps and compare\n", - "// training accuracy and convergence speed.\n", - "//\n", - "// Feature maps to try:\n", - "// - AngleEmbedding(RY)\n", - "// - ZFeatureMap(depth=2)\n", - "// - ZZFeatureMap(depth=2)\n", - "//\n", - "// TODO: Set up the training data\n", - "// TODO: Loop over feature maps, train a VQC with each, and print results\n", - "//\n", - "// Skeleton:\n", - "\n", - "// ctx := context.Background()\n", - "\n", - "// Step 1: Training data (above/below y=x line)\n", - "// trainX := [][]float64{\n", - "// {0.1, 0.9}, {0.2, 0.8}, {0.3, 0.7}, // class 0\n", - "// {0.8, 0.2}, {0.9, 0.1}, {0.7, 0.3}, // class 1\n", - "// }\n", - "// trainY := []int{0, 0, 0, 1, 1, 1}\n", - "\n", - "// Step 2: Define the feature maps to compare\n", - "// type fmEntry struct {\n", - "// name string\n", - "// fm vqc.FeatureMap\n", - "// }\n", - "// featureMaps := []fmEntry{\n", - "// {\"AngleEmbedding(RY)\", vqc.AngleEmbedding(vqc.RotRY)},\n", - "// {\"ZFeatureMap(2)\", vqc.ZFeatureMap(???)},\n", - "// {\"ZZFeatureMap(2)\", vqc.ZZFeatureMap(???)},\n", - "// }\n", - "\n", - "// Step 3: Train and compare\n", - "// fmt.Printf(\"%-22s %10s %10s %10s\\n\", \"Feature Map\", \"Accuracy\", \"Iters\", \"Converged\")\n", - "// for _, entry := range featureMaps {\n", - "// cfg := vqc.Config{\n", - "// NumQubits: 2,\n", - "// FeatureMap: ???,\n", - "// Ansatz: ansatz.NewRealAmplitudes(2, 2, ansatz.Linear),\n", - "// Optimizer: &optim.NelderMead{},\n", - "// TrainX: ???,\n", - "// TrainY: ???,\n", - "// }\n", - "// result, err := vqc.Run(ctx, cfg)\n", - "// // Print: entry.name, result.TrainAccuracy, result.NumIters, result.Converged\n", - "// }\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above, fill in the ???, and run!\")" - ] - }, - { - "cell_type": "markdown", - "id": "f8a9b0c1", - "metadata": {}, - "source": [ - "### Exercise 2 -- Effect of Ansatz Depth\n", - "\n", - "The ansatz depth (number of repetitions) controls model expressivity.\n", - "More layers mean more parameters, which can fit more complex decision\n", - "boundaries but may also overfit or be harder to optimize.\n", - "\n", - "Train the VQC with 1, 2, and 3 repetitions of the RealAmplitudes ansatz.\n", - "Compare the training accuracy and number of parameters." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "a9b0c1d2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above, fill in the ???, and run!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: Effect of Ansatz Depth\n", - "//\n", - "// Goal: Train the VQC with 1, 2, and 3 repetitions of the RealAmplitudes\n", - "// ansatz and compare training accuracy and parameter count.\n", - "//\n", - "// TODO: Set up training data\n", - "// TODO: Loop over repetition counts (1, 2, 3), train, and print results\n", - "//\n", - "// Skeleton:\n", - "\n", - "// ctx := context.Background()\n", - "\n", - "// Step 1: Training data\n", - "// trainX := [][]float64{\n", - "// {0.1, 0.9}, {0.2, 0.8}, {0.3, 0.7},\n", - "// {0.8, 0.2}, {0.9, 0.1}, {0.7, 0.3},\n", - "// }\n", - "// trainY := []int{0, 0, 0, 1, 1, 1}\n", - "\n", - "// Step 2: Sweep ansatz depth\n", - "// fmt.Printf(\"%-8s %8s %10s %10s %10s\\n\",\n", - "// \"Reps\", \"Params\", \"Accuracy\", \"Iters\", \"Converged\")\n", - "//\n", - "// for _, reps := range []int{1, 2, 3} {\n", - "// ans := ansatz.NewRealAmplitudes(2, ???, ansatz.Linear)\n", - "// cfg := vqc.Config{\n", - "// NumQubits: 2,\n", - "// FeatureMap: vqc.AngleEmbedding(vqc.RotRY),\n", - "// Ansatz: ???,\n", - "// Optimizer: ???,\n", - "// TrainX: ???,\n", - "// TrainY: ???,\n", - "// }\n", - "// result, err := vqc.Run(ctx, cfg)\n", - "// // Print: reps, ans.NumParams(), result.TrainAccuracy, result.NumIters, result.Converged\n", - "// }\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above, fill in the ???, and run!\")" - ] - }, - { - "cell_type": "markdown", - "id": "b0c1d2e3", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "\n", - "1. **Quantum feature maps** encode classical data into quantum states.\n", - " Angle embedding maps one feature per qubit (linear). Amplitude\n", - " embedding encodes $2^n$ features in $n$ qubits (exponential\n", - " compression). The ZZ feature map adds entangling gates that capture\n", - " pairwise feature interactions.\n", - "\n", - "2. **The Variational Quantum Classifier (VQC)** is a hybrid model:\n", - " a quantum circuit (feature map + parameterized ansatz) produces\n", - " predictions, and a classical optimizer (Nelder-Mead, Adam, etc.)\n", - " tunes the ansatz parameters to minimize classification loss.\n", - "\n", - "3. **Quantum kernels** measure similarity between data points in quantum\n", - " feature space. The kernel matrix $K_{ij} = |\\langle\\phi(x_j)|\\phi(x_i)\\rangle|^2$\n", - " can be computed on a quantum computer and fed to classical SVM or\n", - " kernel ridge regression.\n", - "\n", - "4. **Feature map choice matters.** Simpler maps (angle embedding) work\n", - " for linearly separable data. More expressive maps (ZZ) may be needed\n", - " for complex decision boundaries, but they also increase circuit depth\n", - " and may be harder to train.\n", - "\n", - "5. **Ansatz depth is a tradeoff.** More repetitions increase expressivity\n", - " but also increase the number of parameters and the risk of barren\n", - " plateaus (vanishing gradients in the cost landscape).\n", - "\n", - "6. **Quantum advantage in ML is not automatic.** The potential advantage\n", - " comes from quantum feature maps that create classically hard-to-compute\n", - " kernels. For many practical datasets, classical models remain\n", - " competitive or superior.\n", - "\n", - "---\n", - "\n", - "**Next up:** Notebook 15, where we will explore quantum error mitigation\n", - "and techniques for reducing the impact of noise on near-term quantum devices." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/15-error-mitigation.ipynb b/notebooks/15-error-mitigation.ipynb deleted file mode 100644 index bfaf7a7..0000000 --- a/notebooks/15-error-mitigation.ipynb +++ /dev/null @@ -1,1390 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "cell-0", - "metadata": {}, - "source": [ - "# 15 -- Error Mitigation\n", - "\n", - "**Prerequisites:** Notebooks 11 and 13. Familiarity with noise models and variational algorithms.\n", - "\n", - "Real quantum hardware is noisy. Every gate, every measurement, every idle\n", - "period introduces errors that corrupt the final result. Full **quantum error\n", - "correction** (QEC) can eliminate these errors, but it requires thousands of\n", - "physical qubits per logical qubit -- far beyond what today's NISQ devices\n", - "offer.\n", - "\n", - "**Error mitigation** is a family of classical post-processing techniques\n", - "that recover more accurate expectation values from noisy hardware *without*\n", - "the qubit overhead of QEC. The key insight: we cannot fix individual shots,\n", - "but we can correct the *statistics* of many shots.\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Describe** the difference between error mitigation and error correction.\n", - "2. **Implement** zero-noise extrapolation to recover accurate expectation values.\n", - "3. **Compare** seven mitigation techniques and their tradeoffs.\n", - "4. **Explain** when each mitigation technique is most appropriate.\n", - "\n", - "In this notebook we will explore six mitigation techniques:\n", - "\n", - "1. **Zero-Noise Extrapolation (ZNE)** -- amplify noise, then extrapolate to zero.\n", - "2. **Readout Mitigation** -- calibrate and invert the measurement confusion matrix.\n", - "3. **Dynamical Decoupling (DD)** -- protect idle qubits with refocusing pulses.\n", - "4. **Pauli Twirling** -- convert coherent errors into stochastic Pauli noise.\n", - "5. **Probabilistic Error Cancellation (PEC)** -- unbiased estimation via quasi-probability sampling.\n", - "6. **Clifford Data Regression (CDR)** -- learn a noise correction model from near-Clifford circuits.\n", - "7. **TREX** -- readout mitigation with O(n) overhead via randomized X insertions.\n", - "\n", - "Each technique has different assumptions, overhead, and accuracy tradeoffs.\n", - "By the end you will understand when to use each one and how to combine them.\n", - "\n", - "### Misconception: Error mitigation is the same as error correction\n", - "\n", - "Error *correction* (QEC) detects and fixes errors in real time during the\n", - "computation, producing fault-tolerant logical qubits. Error *mitigation*\n", - "cannot fix individual errors -- it uses classical post-processing to improve\n", - "the *average* of many noisy measurements. Mitigation is a bridge technology\n", - "for the NISQ era, not a replacement for QEC." - ] - }, - { - "cell_type": "markdown", - "id": "6jecc1yhsp", - "metadata": {}, - "source": [ - "### Why this matters\n", - "\n", - "Until fault-tolerant quantum computers arrive (likely a decade away), **every quantum computation will be noisy**. Error mitigation is how we extract useful results from imperfect hardware *today*. These techniques are the difference between a quantum processor that produces meaningless random numbers and one that gives you actionable scientific results. If you plan to run anything on real hardware, this notebook is essential." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "cell-1", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"context\"\n", - "\t\"fmt\"\n", - "\t\"math\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/algorithm/mitigation\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/circuit/gate\"\n", - "\t\"github.com/splch/goqu/circuit/ir\"\n", - "\t\"github.com/splch/goqu/sim/densitymatrix\"\n", - "\t\"github.com/splch/goqu/sim/noise\"\n", - "\t\"github.com/splch/goqu/sim/pauli\"\n", - "\t\"github.com/splch/goqu/sim/statevector\"\n", - "\t\"github.com/splch/goqu/viz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-2", - "metadata": {}, - "source": [ - "## Setup: A Noisy Bell State\n", - "\n", - "We will use a simple Bell circuit as our test case throughout this notebook.\n", - "The ideal Bell state $|\\Phi^+\\rangle = \\frac{1}{\\sqrt{2}}(|00\\rangle + |11\\rangle)$\n", - "has $\\langle ZZ \\rangle = +1$ because both qubits always agree.\n", - "\n", - "Under depolarizing noise, this expectation value decays toward zero (the\n", - "maximally mixed value). Our goal is to recover the ideal $\\langle ZZ \\rangle = 1$\n", - "from noisy measurements.\n", - "\n", - "We set up:\n", - "- An **ideal executor** using noiseless statevector simulation.\n", - "- A **noisy executor** using density matrix simulation with depolarizing noise\n", - " (2% on single-qubit gates, 5% on two-qubit gates)." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "cell-3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Bell circuit:\n", - "Ideal : 1.0000\n", - "Noisy : 0.9467\n", - "\n", - "Error: 0.0533 (5.3% of ideal)\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Build a Bell circuit: H(0), CNOT(0,1)\n", - "c, err := builder.New(\"bell\", 2).H(0).CNOT(0, 1).Build()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "fmt.Println(\"Bell circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "\n", - "// Observable: ZZ correlation\n", - "zz, err := pauli.Parse(\"ZZ\")\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "ham, err := pauli.NewPauliSum([]pauli.PauliString{zz})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "// Ideal expectation: noiseless statevector simulation\n", - "idealExec := mitigation.StatevectorExecutor(ham)\n", - "ctx := context.Background()\n", - "ideal, err := idealExec(ctx, c)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "fmt.Printf(\"Ideal : %.4f\\n\", ideal)\n", - "\n", - "// Noisy expectation: density matrix with depolarizing noise\n", - "nm := noise.New()\n", - "nm.AddGateError(\"H\", noise.Depolarizing1Q(0.02))\n", - "nm.AddGateError(\"CNOT\", noise.Depolarizing2Q(0.05))\n", - "noisyExec := mitigation.DensityMatrixExecutor(ham, nm)\n", - "noisy, err := noisyExec(ctx, c)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "fmt.Printf(\"Noisy : %.4f\\n\", noisy)\n", - "fmt.Printf(\"\\nError: %.4f (%.1f%% of ideal)\\n\", math.Abs(ideal-noisy), 100*math.Abs(ideal-noisy)/math.Abs(ideal))" - ] - }, - { - "cell_type": "markdown", - "id": "cell-4", - "metadata": {}, - "source": [ - "## Zero-Noise Extrapolation (ZNE)\n", - "\n", - "ZNE is the most widely used error mitigation technique. The idea is\n", - "beautifully simple:\n", - "\n", - "1. **Amplify** the noise at several known scale factors (1x, 3x, 5x, ...)\n", - " by inserting identity-equivalent gate sequences (unitary folding):\n", - " $C \\to C(C^\\dagger C)^k$\n", - "2. **Measure** the expectation value at each noise level.\n", - "3. **Extrapolate** back to the zero-noise limit using polynomial or\n", - " exponential fitting.\n", - "\n", - "The key assumption is that the expectation value varies smoothly with\n", - "noise strength. By sampling at multiple noise levels and fitting a curve,\n", - "we can estimate what the noiseless value would have been.\n", - "\n", - "| Extrapolator | Fit function | Best for |\n", - "|:---|:---|:---|\n", - "| Linear | $y = a + bx$ | Low noise, few scale factors |\n", - "| Polynomial | Degree $n-1$ polynomial | More scale factors, moderate noise |\n", - "| Exponential | $y = a + be^{cx}$ | High noise with exponential decay |" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "cell-5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ZNE mitigated : 0.9916\n", - "Ideal value: 1.0000\n", - "Noisy value: 0.9467\n", - "\n", - "Noise-scaled values:\n", - " scale=1: = 0.9467\n", - " scale=3: = 0.8484\n", - " scale=5: = 0.7603\n", - "\n", - "ZNE error: 0.0084\n", - "Noisy error: 0.0533\n", - "Improvement: 6.3x closer to ideal\n" - ] - } - ], - "source": [ - "%%\n", - "// Run ZNE with default linear extrapolation\n", - "\n", - "// Re-create the Bell circuit and executors in this cell's scope\n", - "c, _ := builder.New(\"bell\", 2).H(0).CNOT(0, 1).Build()\n", - "zz, _ := pauli.Parse(\"ZZ\")\n", - "ham, _ := pauli.NewPauliSum([]pauli.PauliString{zz})\n", - "idealExec := mitigation.StatevectorExecutor(ham)\n", - "ctx := context.Background()\n", - "ideal, _ := idealExec(ctx, c)\n", - "nm := noise.New()\n", - "nm.AddGateError(\"H\", noise.Depolarizing1Q(0.02))\n", - "nm.AddGateError(\"CNOT\", noise.Depolarizing2Q(0.05))\n", - "noisyExec := mitigation.DensityMatrixExecutor(ham, nm)\n", - "noisy, _ := noisyExec(ctx, c)\n", - "\n", - "zneResult, err := mitigation.RunZNE(ctx, mitigation.ZNEConfig{\n", - "\tCircuit: c,\n", - "\tExecutor: noisyExec,\n", - "\tScaleFactors: []float64{1, 3, 5},\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"ZNE mitigated : %.4f\\n\", zneResult.MitigatedValue)\n", - "fmt.Printf(\"Ideal value: %.4f\\n\", ideal)\n", - "fmt.Printf(\"Noisy value: %.4f\\n\", noisy)\n", - "fmt.Println()\n", - "\n", - "fmt.Println(\"Noise-scaled values:\")\n", - "for i, sf := range zneResult.ScaleFactors {\n", - "\tfmt.Printf(\" scale=%.0f: = %.4f\\n\", sf, zneResult.NoisyValues[i])\n", - "}\n", - "\n", - "fmt.Printf(\"\\nZNE error: %.4f\\n\", math.Abs(ideal-zneResult.MitigatedValue))\n", - "fmt.Printf(\"Noisy error: %.4f\\n\", math.Abs(ideal-noisy))\n", - "fmt.Printf(\"Improvement: %.1fx closer to ideal\\n\",\n", - "\tmath.Abs(ideal-noisy)/math.Max(math.Abs(ideal-zneResult.MitigatedValue), 1e-10))" - ] - }, - { - "cell_type": "markdown", - "id": "cell-6", - "metadata": {}, - "source": [ - "## Readout Mitigation\n", - "\n", - "Even if the quantum state is perfect, the **measurement apparatus** can\n", - "misread qubits: a qubit in $|0\\rangle$ might be read as 1 (probability\n", - "$P(1|0)$) and vice versa ($P(0|1)$). These readout errors are purely\n", - "classical and can be corrected.\n", - "\n", - "The standard approach:\n", - "\n", - "1. **Calibrate**: prepare each computational basis state $|00\\rangle$,\n", - " $|01\\rangle$, $|10\\rangle$, $|11\\rangle$ and measure many times.\n", - " This builds a **confusion matrix** $A$ where $A_{ij}$ = P(measure $i$\n", - " | prepared $j$).\n", - "2. **Correct**: multiply raw measurement probabilities by $A^{-1}$.\n", - "\n", - "For $n$ qubits, full calibration requires $2^n$ basis states -- exponential\n", - "cost. For small qubit counts this is fine; for larger systems, per-qubit\n", - "calibration (tensor product approximation) scales linearly.\n", - "\n", - "Let's build a noisy measurement setup and calibrate it." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "cell-7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Raw counts (with readout error):\n", - " |10>: 403\n", - " |11>: 4585\n", - " |00>: 4602\n", - " |01>: 410\n", - "\n", - "Corrected counts:\n", - " |00>: 4833\n", - " |01>: 129\n", - " |10>: 106\n", - " |11>: 4932\n", - "\n", - "Ideal Bell state: |00> and |11> each ~50%.\n", - "Readout mitigation removes the spurious |01> and |10> counts.\n" - ] - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Set up a noise model with readout errors\n", - "nmReadout := noise.New()\n", - "nmReadout.AddGateError(\"H\", noise.Depolarizing1Q(0.02))\n", - "nmReadout.AddGateError(\"CNOT\", noise.Depolarizing2Q(0.05))\n", - "nmReadout.AddReadoutError(0, noise.NewReadoutError(0.03, 0.04)) // P(1|0)=3%, P(0|1)=4%\n", - "nmReadout.AddReadoutError(1, noise.NewReadoutError(0.02, 0.03)) // P(1|0)=2%, P(0|1)=3%\n", - "\n", - "// Create a basis executor for calibration.\n", - "// This prepares a computational basis state and measures it.\n", - "basisExec := func(ctx context.Context, basisState int, shots int) (map[string]int, error) {\n", - "\t// Build a circuit that prepares the basis state\n", - "\tnumQubits := 2\n", - "\tvar ops []ir.Operation\n", - "\tfor q := 0; q < numQubits; q++ {\n", - "\t\tif (basisState>>q)&1 == 1 {\n", - "\t\t\tops = append(ops, ir.Operation{Gate: gate.X, Qubits: []int{q}})\n", - "\t\t}\n", - "\t}\n", - "\t// Add measurements\n", - "\tfor q := 0; q < numQubits; q++ {\n", - "\t\tops = append(ops, ir.Operation{Gate: nil, Qubits: []int{q}, Clbits: []int{q}})\n", - "\t}\n", - "\tcirc := ir.New(\"calib\", numQubits, numQubits, ops, nil)\n", - "\n", - "\tsim := densitymatrix.New(numQubits).WithNoise(nmReadout)\n", - "\treturn sim.Run(circ, shots)\n", - "}\n", - "\n", - "// Calibrate: measure all 4 basis states\n", - "cal, err := mitigation.CalibrateReadout(ctx, 2, 10000, basisExec)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "// Run the Bell circuit with noisy readout\n", - "measC, err := builder.New(\"meas\", 2).H(0).CNOT(0, 1).MeasureAll().Build()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "simNoisy := densitymatrix.New(2).WithNoise(nmReadout)\n", - "rawCounts, err := simNoisy.Run(measC, 10000)\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "// Correct the counts\n", - "corrected := cal.CorrectCounts(rawCounts)\n", - "\n", - "fmt.Println(\"Raw counts (with readout error):\")\n", - "for bs, count := range rawCounts {\n", - "\tfmt.Printf(\" |%s>: %d\\n\", bs, count)\n", - "}\n", - "\n", - "fmt.Println(\"\\nCorrected counts:\")\n", - "for bs, count := range corrected {\n", - "\tfmt.Printf(\" |%s>: %d\\n\", bs, count)\n", - "}\n", - "\n", - "fmt.Println(\"\\nIdeal Bell state: |00> and |11> each ~50%.\")\n", - "fmt.Println(\"Readout mitigation removes the spurious |01> and |10> counts.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-8", - "metadata": {}, - "source": [ - "## Dynamical Decoupling (DD)\n", - "\n", - "When a qubit sits idle while other qubits are being operated on, it\n", - "accumulates errors from interactions with the environment (decoherence).\n", - "**Dynamical decoupling** inserts refocusing pulse sequences into these idle\n", - "periods to average out the unwanted interactions.\n", - "\n", - "The simplest DD sequence is **XX**: two X gates separated by equal idle\n", - "time. Since $X \\cdot X = I$, the net unitary is identity, but the pulses\n", - "refocus low-frequency noise. The **XY4** sequence ($X$-$Y$-$X$-$Y$)\n", - "provides higher-order decoupling.\n", - "\n", - "| Sequence | Pulses | Decoupling order | Overhead |\n", - "|:---|:---|:---|:---|\n", - "| DDXX | X, X | 1st order | 2 gates per idle gap |\n", - "| DDXY4 | X, Y, X, Y | 2nd order | 4 gates per idle gap |\n", - "\n", - "DD is a **circuit transform** -- it modifies the circuit before execution,\n", - "requiring no additional measurements or post-processing." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "cell-9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Original circuit:\n", - "Original gates: 7\n", - "\n", - "With DD (XX sequence):\n", - "DD gates: 11\n", - "Added 4 refocusing pulses\n", - "\n", - "The DD pulses fill idle periods on qubits 1 and 2,\n", - "protecting them from decoherence while qubit 0 is busy.\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "H\n", - "X\n", - "H\n", - "X\n", - "H\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "q2\n", - "\n", - "H\n", - "X\n", - "X\n", - "X\n", - "H\n", - "X\n", - "X\n", - "H\n", - "X\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "// Build a circuit with idle qubit periods:\n", - "// Qubit 0 has multiple gates, qubit 1 idles until the CNOT.\n", - "cDD, err := builder.New(\"dd_demo\", 3).\n", - "\tH(0).\n", - "\tX(0).\n", - "\tH(0).\n", - "\tX(0).\n", - "\tH(0).\n", - "\tCNOT(0, 1).\n", - "\tCNOT(0, 2).\n", - "\tBuild()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"Original circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(cDD))\n", - "fmt.Printf(\"Original gates: %d\\n\\n\", len(cDD.Ops()))\n", - "\n", - "// Insert XX dynamical decoupling\n", - "ddCircuit, err := mitigation.InsertDD(mitigation.DDConfig{\n", - "\tCircuit: cDD,\n", - "\tSequence: mitigation.DDXX,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"With DD (XX sequence):\")\n", - "gonbui.DisplayHTML(draw.SVG(ddCircuit))\n", - "fmt.Printf(\"DD gates: %d\\n\", len(ddCircuit.Ops()))\n", - "fmt.Printf(\"Added %d refocusing pulses\\n\", len(ddCircuit.Ops())-len(cDD.Ops()))\n", - "\n", - "fmt.Println(\"\\nThe DD pulses fill idle periods on qubits 1 and 2,\")\n", - "fmt.Println(\"protecting them from decoherence while qubit 0 is busy.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-10", - "metadata": {}, - "source": [ - "## Pauli Twirling\n", - "\n", - "Real noise on two-qubit gates is often **coherent** -- it has a preferred\n", - "direction in the error space and can add constructively across gates.\n", - "Coherent errors are harder to model and mitigate than random (stochastic)\n", - "errors.\n", - "\n", - "**Pauli twirling** converts coherent errors into stochastic Pauli errors\n", - "by inserting random Pauli gates before and after each two-qubit gate. The\n", - "Pauli pair is chosen so that the ideal gate is preserved:\n", - "\n", - "$$(P_a \\otimes P_b) \\cdot G \\cdot (P_c \\otimes P_d) = G$$\n", - "\n", - "where $(P_c, P_d)$ is determined by the conjugation table of $G$.\n", - "\n", - "Averaging over many random twirls converts the noise channel into a\n", - "**Pauli channel** -- diagonal in the Pauli basis -- which is easier for\n", - "other techniques (ZNE, PEC) to handle.\n", - "\n", - "Twirling does not reduce the total error rate, but it makes the error\n", - "structure more predictable and well-behaved." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "cell-11", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Twirled : 0.9467\n", - "Noisy : 0.9467\n", - "Ideal : 1.0000\n", - "\n", - "Std dev across 100 twirled circuits: 0.0000\n", - "\n", - "Twirling converts coherent errors into stochastic Pauli noise.\n", - "The average may not improve, but the variance is well-characterized.\n" - ] - } - ], - "source": [ - "%%\n", - "c, _ := builder.New(\"bell\", 2).H(0).CNOT(0, 1).Build()\n", - "zz, _ := pauli.Parse(\"ZZ\")\n", - "ham, _ := pauli.NewPauliSum([]pauli.PauliString{zz})\n", - "idealExec := mitigation.StatevectorExecutor(ham)\n", - "ctx := context.Background()\n", - "ideal, _ := idealExec(ctx, c)\n", - "nm := noise.New()\n", - "nm.AddGateError(\"H\", noise.Depolarizing1Q(0.02))\n", - "nm.AddGateError(\"CNOT\", noise.Depolarizing2Q(0.05))\n", - "noisyExec := mitigation.DensityMatrixExecutor(ham, nm)\n", - "noisy, _ := noisyExec(ctx, c)\n", - "\n", - "// Run Pauli twirling on the Bell circuit\n", - "twirlResult, err := mitigation.RunTwirl(ctx, mitigation.TwirlConfig{\n", - "\tCircuit: c,\n", - "\tExecutor: noisyExec,\n", - "\tSamples: 100,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"Twirled : %.4f\\n\", twirlResult.MitigatedValue)\n", - "fmt.Printf(\"Noisy : %.4f\\n\", noisy)\n", - "fmt.Printf(\"Ideal : %.4f\\n\", ideal)\n", - "\n", - "// Compute standard deviation of the twirled samples\n", - "mean := twirlResult.MitigatedValue\n", - "sumSq := 0.0\n", - "for _, v := range twirlResult.RawValues {\n", - "\tsumSq += (v - mean) * (v - mean)\n", - "}\n", - "std := math.Sqrt(sumSq / float64(len(twirlResult.RawValues)))\n", - "fmt.Printf(\"\\nStd dev across %d twirled circuits: %.4f\\n\", len(twirlResult.RawValues), std)\n", - "fmt.Println(\"\\nTwirling converts coherent errors into stochastic Pauli noise.\")\n", - "fmt.Println(\"The average may not improve, but the variance is well-characterized.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-12", - "metadata": {}, - "source": [ - "## Probabilistic Error Cancellation (PEC)\n", - "\n", - "PEC provides **unbiased** estimation of the ideal expectation value -- in\n", - "the limit of infinite samples, it converges to the exact noiseless result.\n", - "This makes it the gold standard for accuracy, but at a steep cost in\n", - "sampling overhead.\n", - "\n", - "The idea: if we know the noise channel $\\mathcal{N}$ on each gate, we can\n", - "decompose its inverse $\\mathcal{N}^{-1}$ as a **quasi-probability\n", - "distribution** over Pauli corrections:\n", - "\n", - "$$\\mathcal{N}^{-1} = \\sum_i \\eta_i \\, \\mathcal{P}_i$$\n", - "\n", - "where $\\eta_i$ can be negative (hence \"quasi-probability\"). We sample\n", - "corrections from the distribution $|\\eta_i| / \\gamma$ and weight each\n", - "result by $\\text{sign}(\\eta_i) \\cdot \\gamma$, where $\\gamma = \\sum |\\eta_i|$\n", - "is the **sampling overhead**.\n", - "\n", - "The overhead grows exponentially with circuit depth: $\\gamma^L$ where $L$\n", - "is the number of noisy gates. This limits PEC to short circuits with\n", - "well-characterized noise.\n", - "\n", - "**Requirement**: PEC needs a known noise model (currently depolarizing)." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "cell-13", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "PEC mitigated : 1.0287\n", - "Ideal : 1.0000\n", - "Noisy : 0.9467\n", - "\n", - "Sampling overhead (gamma): 1.15\n", - "PEC error: 0.0287\n", - "Noisy error: 0.0533\n", - "\n", - "PEC is unbiased: with enough samples, it converges to the exact ideal value.\n", - "The cost is the sampling overhead, which scales exponentially with circuit depth.\n" - ] - } - ], - "source": [ - "%%\n", - "c, _ := builder.New(\"bell\", 2).H(0).CNOT(0, 1).Build()\n", - "zz, _ := pauli.Parse(\"ZZ\")\n", - "ham, _ := pauli.NewPauliSum([]pauli.PauliString{zz})\n", - "idealExec := mitigation.StatevectorExecutor(ham)\n", - "ctx := context.Background()\n", - "ideal, _ := idealExec(ctx, c)\n", - "nm := noise.New()\n", - "nm.AddGateError(\"H\", noise.Depolarizing1Q(0.02))\n", - "nm.AddGateError(\"CNOT\", noise.Depolarizing2Q(0.05))\n", - "noisyExec := mitigation.DensityMatrixExecutor(ham, nm)\n", - "noisy, _ := noisyExec(ctx, c)\n", - "\n", - "// Run Probabilistic Error Cancellation\n", - "pecResult, err := mitigation.RunPEC(ctx, mitigation.PECConfig{\n", - "\tCircuit: c,\n", - "\tExecutor: noisyExec,\n", - "\tNoiseModel: nm,\n", - "\tSamples: 500,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"PEC mitigated : %.4f\\n\", pecResult.MitigatedValue)\n", - "fmt.Printf(\"Ideal : %.4f\\n\", ideal)\n", - "fmt.Printf(\"Noisy : %.4f\\n\", noisy)\n", - "fmt.Printf(\"\\nSampling overhead (gamma): %.2f\\n\", pecResult.Overhead)\n", - "fmt.Printf(\"PEC error: %.4f\\n\", math.Abs(ideal-pecResult.MitigatedValue))\n", - "fmt.Printf(\"Noisy error: %.4f\\n\", math.Abs(ideal-noisy))\n", - "\n", - "fmt.Println(\"\\nPEC is unbiased: with enough samples, it converges to the exact ideal value.\")\n", - "fmt.Println(\"The cost is the sampling overhead, which scales exponentially with circuit depth.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-14", - "metadata": {}, - "source": [ - "## Clifford Data Regression (CDR)\n", - "\n", - "CDR is a **learning-based** mitigation technique. Instead of requiring a\n", - "full noise model, it learns a correction function from data.\n", - "\n", - "The recipe:\n", - "\n", - "1. Generate **near-Clifford training circuits** by replacing a fraction of\n", - " non-Clifford gates (T, parameterized rotations) with their nearest\n", - " Clifford equivalents (S, Pauli gates).\n", - "2. Run each training circuit on **both** the noisy executor and an ideal\n", - " simulator. Clifford circuits can be efficiently simulated classically.\n", - "3. Fit an **affine model**: $\\text{ideal} = a \\cdot \\text{noisy} + b$.\n", - "4. Apply the correction to the original noisy result.\n", - "\n", - "CDR works best when the circuit contains non-Clifford gates (otherwise\n", - "the training circuits are too similar to the original). For our pure-Clifford\n", - "Bell circuit, we will add T gates to demonstrate CDR properly.\n", - "\n", - "**Advantage**: No noise model needed -- CDR is model-free." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "cell-15", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CDR circuit (with T gates):\n", - "Ideal : 1.0000\n", - "Noisy : 0.9467\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "T\n", - "\n", - "\n", - "\n", - "T\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "CDR mitigated : 1.0000\n", - "Affine fit: ideal = -0.0489 * noisy + 1.0463\n", - "\n", - "CDR error: 0.0000\n", - "Noisy error: 0.0533\n" - ] - } - ], - "source": [ - "%%\n", - "// CDR needs non-Clifford gates. Build a circuit with T gates.\n", - "ops := []ir.Operation{\n", - "\t{Gate: gate.H, Qubits: []int{0}},\n", - "\t{Gate: gate.T, Qubits: []int{0}},\n", - "\t{Gate: gate.CNOT, Qubits: []int{0, 1}},\n", - "\t{Gate: gate.T, Qubits: []int{1}},\n", - "}\n", - "cdrCirc := ir.New(\"t_circuit\", 2, 0, ops, nil)\n", - "\n", - "fmt.Println(\"CDR circuit (with T gates):\")\n", - "gonbui.DisplayHTML(draw.SVG(cdrCirc))\n", - "\n", - "// Ideal and noisy values for this circuit\n", - "zz, _ := pauli.Parse(\"ZZ\")\n", - "ham, _ := pauli.NewPauliSum([]pauli.PauliString{zz})\n", - "idealExec := mitigation.StatevectorExecutor(ham)\n", - "ctx := context.Background()\n", - "nm := noise.New()\n", - "nm.AddGateError(\"H\", noise.Depolarizing1Q(0.02))\n", - "nm.AddGateError(\"CNOT\", noise.Depolarizing2Q(0.05))\n", - "noisyExec := mitigation.DensityMatrixExecutor(ham, nm)\n", - "\n", - "cdrIdeal, _ := idealExec(ctx, cdrCirc)\n", - "cdrNoisy, _ := noisyExec(ctx, cdrCirc)\n", - "fmt.Printf(\"Ideal : %.4f\\n\", cdrIdeal)\n", - "fmt.Printf(\"Noisy : %.4f\\n\", cdrNoisy)\n", - "\n", - "// Run CDR\n", - "cdrResult, err := mitigation.RunCDR(ctx, mitigation.CDRConfig{\n", - "\tCircuit: cdrCirc,\n", - "\tExecutor: noisyExec,\n", - "\tHamiltonian: ham,\n", - "\tNumTraining: 20,\n", - "\tFraction: 0.75,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Printf(\"\\nCDR mitigated : %.4f\\n\", cdrResult.MitigatedValue)\n", - "fmt.Printf(\"Affine fit: ideal = %.4f * noisy + %.4f\\n\", cdrResult.FitA, cdrResult.FitB)\n", - "fmt.Printf(\"\\nCDR error: %.4f\\n\", math.Abs(cdrIdeal-cdrResult.MitigatedValue))\n", - "fmt.Printf(\"Noisy error: %.4f\\n\", math.Abs(cdrIdeal-cdrNoisy))" - ] - }, - { - "cell_type": "markdown", - "id": "cell-16", - "metadata": {}, - "source": [ - "## TREX (Twirled Readout Error eXtinction)\n", - "\n", - "Full readout calibration requires $2^n$ basis state preparations --\n", - "exponential in qubit count. **TREX** provides readout error mitigation\n", - "with only **O(n)** overhead.\n", - "\n", - "The idea: before each measurement, randomly insert an X gate (or not)\n", - "on each qubit. After measurement, classically undo the bit flip. By\n", - "averaging over many random twirls, the readout error is converted into\n", - "a symmetric (depolarizing) readout error that can be corrected with a\n", - "simple per-qubit scale factor.\n", - "\n", - "TREX is particularly useful for systems with many qubits where full\n", - "confusion matrix calibration is impractical." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "cell-17", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TREX corrected counts:\n", - " |01>: 248 (2.5%)\n", - " |10>: 212 (2.1%)\n", - " |00>: 4736 (47.4%)\n", - " |11>: 4804 (48.0%)\n", - "\n", - "TREX uses O(n) calibration overhead instead of O(2^n).\n", - "Random X insertions symmetrize the readout error.\n" - ] - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Recreate readout noise model\n", - "nmReadout := noise.New()\n", - "nmReadout.AddGateError(\"H\", noise.Depolarizing1Q(0.02))\n", - "nmReadout.AddGateError(\"CNOT\", noise.Depolarizing2Q(0.05))\n", - "nmReadout.AddReadoutError(0, noise.NewReadoutError(0.03, 0.04))\n", - "nmReadout.AddReadoutError(1, noise.NewReadoutError(0.02, 0.03))\n", - "\n", - "// Build a Bell circuit with measurements for TREX\n", - "trexCirc, err := builder.New(\"trex_bell\", 2).H(0).CNOT(0, 1).MeasureAll().Build()\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "// Shot-based runner with readout noise\n", - "runner := func(ctx context.Context, circ *ir.Circuit, shots int) (map[string]int, error) {\n", - "\tsim := densitymatrix.New(circ.NumQubits()).WithNoise(nmReadout)\n", - "\treturn sim.Run(circ, shots)\n", - "}\n", - "\n", - "// Run TREX\n", - "trexResult, err := mitigation.RunTREX(ctx, mitigation.TREXConfig{\n", - "\tCircuit: trexCirc,\n", - "\tRunner: runner,\n", - "\tShots: 1000,\n", - "\tSamples: 10,\n", - "\tCalibShots: 5000,\n", - "})\n", - "if err != nil {\n", - "\tpanic(err)\n", - "}\n", - "\n", - "fmt.Println(\"TREX corrected counts:\")\n", - "total := 0\n", - "for _, cnt := range trexResult.Counts {\n", - "\ttotal += cnt\n", - "}\n", - "for bs, cnt := range trexResult.Counts {\n", - "\tfmt.Printf(\" |%s>: %d (%.1f%%)\\n\", bs, cnt, 100*float64(cnt)/float64(total))\n", - "}\n", - "\n", - "fmt.Println(\"\\nTREX uses O(n) calibration overhead instead of O(2^n).\")\n", - "fmt.Println(\"Random X insertions symmetrize the readout error.\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-18", - "metadata": {}, - "source": [ - "## Comparison of All Techniques\n", - "\n", - "Let's compare all mitigation techniques side by side on our Bell circuit.\n", - "For a fair comparison, we use the same noise model and observable\n", - "($\\langle ZZ \\rangle$) throughout.\n", - "\n", - "| Technique | Noise model needed? | Unbiased? | Overhead | Best for |\n", - "|:---|:---|:---|:---|:---|\n", - "| ZNE | No | No (biased extrapolation) | Low (a few extra circuits) | General purpose |\n", - "| Readout | Calibration only | Yes (invertible) | $2^n$ calibration shots | Measurement errors |\n", - "| DD | No | N/A (preventive) | Zero extra shots | Idle qubit decoherence |\n", - "| Twirling | No | No (converts error type) | $k$ twirled copies | Coherent errors |\n", - "| PEC | Yes (depolarizing) | Yes | $\\gamma^L$ (exponential) | Short circuits |\n", - "| CDR | No | No (affine approximation) | $k$ training circuits | Non-Clifford circuits |\n", - "| TREX | Calibration only | Approximately | $O(n)$ calibration | Large qubit count readout |" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "cell-19", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "============================================================\n", - " Technique Error Improvement\n", - "============================================================\n", - " Ideal +1.0000 0.0000 \n", - " Noisy (baseline) +0.9467 0.0533 \n", - " ZNE +0.9916 0.0084 6.3x\n", - " Pauli Twirling +0.9467 0.0533 1.0x\n", - " PEC +0.9981 0.0019 28.8x\n", - " CDR +1.0000 0.0000 >100x\n", - "============================================================\n" - ] - } - ], - "source": [ - "%%\n", - "// Run all expectation-value-based techniques on the Bell circuit\n", - "// and collect results for comparison.\n", - "\n", - "// Re-create the Bell circuit and executors in this cell's scope\n", - "c, _ := builder.New(\"bell\", 2).H(0).CNOT(0, 1).Build()\n", - "zz, _ := pauli.Parse(\"ZZ\")\n", - "ham, _ := pauli.NewPauliSum([]pauli.PauliString{zz})\n", - "idealExec := mitigation.StatevectorExecutor(ham)\n", - "ctx := context.Background()\n", - "ideal, _ := idealExec(ctx, c)\n", - "nm := noise.New()\n", - "nm.AddGateError(\"H\", noise.Depolarizing1Q(0.02))\n", - "nm.AddGateError(\"CNOT\", noise.Depolarizing2Q(0.05))\n", - "noisyExec := mitigation.DensityMatrixExecutor(ham, nm)\n", - "noisy, _ := noisyExec(ctx, c)\n", - "\n", - "// ZNE\n", - "zneResult, _ := mitigation.RunZNE(ctx, mitigation.ZNEConfig{\n", - "\tCircuit: c,\n", - "\tExecutor: noisyExec,\n", - "\tScaleFactors: []float64{1, 3, 5},\n", - "})\n", - "zneVal := zneResult.MitigatedValue\n", - "\n", - "// Twirling\n", - "twirlResult, _ := mitigation.RunTwirl(ctx, mitigation.TwirlConfig{\n", - "\tCircuit: c,\n", - "\tExecutor: noisyExec,\n", - "\tSamples: 100,\n", - "})\n", - "twirlVal := twirlResult.MitigatedValue\n", - "\n", - "// PEC\n", - "pecResult, _ := mitigation.RunPEC(ctx, mitigation.PECConfig{\n", - "\tCircuit: c,\n", - "\tExecutor: noisyExec,\n", - "\tNoiseModel: nm,\n", - "\tSamples: 500,\n", - "})\n", - "pecVal := pecResult.MitigatedValue\n", - "\n", - "// CDR on the Bell circuit (Clifford-only, so CDR has limited benefit here)\n", - "cdrBellResult, err := mitigation.RunCDR(ctx, mitigation.CDRConfig{\n", - "\tCircuit: c,\n", - "\tExecutor: noisyExec,\n", - "\tHamiltonian: ham,\n", - "\tNumTraining: 20,\n", - "})\n", - "cdrBellVal := 0.0\n", - "if err == nil {\n", - "\tcdrBellVal = cdrBellResult.MitigatedValue\n", - "}\n", - "\n", - "// Print comparison table\n", - "fmt.Println(\"============================================================\")\n", - "fmt.Println(\" Technique Error Improvement\")\n", - "fmt.Println(\"============================================================\")\n", - "\n", - "noisyErr := math.Abs(ideal - noisy)\n", - "rows := []struct {\n", - "\tname string\n", - "\tval float64\n", - "}{\n", - "\t{\"Ideal\", ideal},\n", - "\t{\"Noisy (baseline)\", noisy},\n", - "\t{\"ZNE\", zneVal},\n", - "\t{\"Pauli Twirling\", twirlVal},\n", - "\t{\"PEC\", pecVal},\n", - "\t{\"CDR\", cdrBellVal},\n", - "}\n", - "\n", - "for _, r := range rows {\n", - "\terr := math.Abs(ideal - r.val)\n", - "\timprovement := \"-\"\n", - "\tif r.name != \"Ideal\" && r.name != \"Noisy (baseline)\" && err > 1e-10 {\n", - "\t\timprovement = fmt.Sprintf(\"%.1fx\", noisyErr/err)\n", - "\t} else if r.name != \"Ideal\" && r.name != \"Noisy (baseline)\" {\n", - "\t\timprovement = \">100x\"\n", - "\t}\n", - "\tif r.name == \"Ideal\" || r.name == \"Noisy (baseline)\" {\n", - "\t\timprovement = \"\"\n", - "\t}\n", - "\tfmt.Printf(\" %-20s %+.4f %.4f %s\\n\", r.name, r.val, err, improvement)\n", - "}\n", - "fmt.Println(\"============================================================\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-20", - "metadata": {}, - "source": [ - "## Predict, Then Verify\n", - "\n", - "**Question:** If we increase the noise strength, which technique degrades\n", - "most gracefully?\n", - "\n", - "**Prediction:**\n", - "- **ZNE** should still help at moderate noise but will struggle at very high\n", - " noise because the extrapolation curve becomes unreliable.\n", - "- **PEC** remains unbiased but the sampling overhead $\\gamma^L$ grows\n", - " rapidly, increasing the variance.\n", - "- **Twirling** does not reduce the error magnitude, so it should track\n", - " the noisy value.\n", - "\n", - "Let's verify by testing at low (1%), medium (5%), and high (15%) noise." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "cell-21", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Technique performance vs noise strength\n", - "========================================\n", - "Noise Noisy ZNE PEC Twirl\n", - "------------------------------------------------\n", - " 1% +0.9787 +0.9986 +1.0061 +0.9787\n", - " 5% +0.8933 +0.9684 +0.9781 +0.8933\n", - " 10% +0.7867 +0.8890 +1.0282 +0.7867\n", - " 15% +0.6800 +0.7809 +0.9645 +0.6800\n", - "\n", - "Ideal = 1.0000\n", - "\n", - "As predicted: ZNE and PEC improve over the noisy baseline at all noise levels.\n", - "PEC remains unbiased. Twirling tracks the noisy value (it reshapes, not reduces).\n" - ] - } - ], - "source": [ - "%%\n", - "// Re-create the Bell circuit and executors in this cell's scope\n", - "c, _ := builder.New(\"bell\", 2).H(0).CNOT(0, 1).Build()\n", - "zz, _ := pauli.Parse(\"ZZ\")\n", - "ham, _ := pauli.NewPauliSum([]pauli.PauliString{zz})\n", - "idealExec := mitigation.StatevectorExecutor(ham)\n", - "ctx := context.Background()\n", - "ideal, _ := idealExec(ctx, c)\n", - "\n", - "fmt.Println(\"Technique performance vs noise strength\")\n", - "fmt.Println(\"========================================\")\n", - "fmt.Printf(\"%-8s %8s %8s %8s %8s\\n\", \"Noise\", \"Noisy\", \"ZNE\", \"PEC\", \"Twirl\")\n", - "fmt.Println(\"------------------------------------------------\")\n", - "\n", - "for _, p := range []float64{0.01, 0.05, 0.10, 0.15} {\n", - "\tnmTest := noise.New()\n", - "\tnmTest.AddGateError(\"H\", noise.Depolarizing1Q(p))\n", - "\tnmTest.AddGateError(\"CNOT\", noise.Depolarizing2Q(2*p))\n", - "\ttestExec := mitigation.DensityMatrixExecutor(ham, nmTest)\n", - "\n", - "\tnoisyVal, _ := testExec(ctx, c)\n", - "\n", - "\tzneRes, _ := mitigation.RunZNE(ctx, mitigation.ZNEConfig{\n", - "\t\tCircuit: c,\n", - "\t\tExecutor: testExec,\n", - "\t\tScaleFactors: []float64{1, 3, 5},\n", - "\t})\n", - "\n", - "\tpecRes, _ := mitigation.RunPEC(ctx, mitigation.PECConfig{\n", - "\t\tCircuit: c,\n", - "\t\tExecutor: testExec,\n", - "\t\tNoiseModel: nmTest,\n", - "\t\tSamples: 500,\n", - "\t})\n", - "\n", - "\ttwirlRes, _ := mitigation.RunTwirl(ctx, mitigation.TwirlConfig{\n", - "\t\tCircuit: c,\n", - "\t\tExecutor: testExec,\n", - "\t\tSamples: 100,\n", - "\t})\n", - "\n", - "\tfmt.Printf(\"%5.0f%% %+.4f %+.4f %+.4f %+.4f\\n\",\n", - "\t\tp*100, noisyVal,\n", - "\t\tzneRes.MitigatedValue,\n", - "\t\tpecRes.MitigatedValue,\n", - "\t\ttwirlRes.MitigatedValue)\n", - "}\n", - "\n", - "fmt.Printf(\"\\nIdeal = %.4f\\n\", ideal)\n", - "fmt.Println(\"\\nAs predicted: ZNE and PEC improve over the noisy baseline at all noise levels.\")\n", - "fmt.Println(\"PEC remains unbiased. Twirling tracks the noisy value (it reshapes, not reduces).\")" - ] - }, - { - "cell_type": "markdown", - "id": "imght6mmbzc", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. Why does ZNE work even though we cannot reduce noise below the hardware level?\n", - "2. What is the main disadvantage of PEC compared to ZNE in terms of scalability?\n", - "3. True or false: Pauli twirling reduces the total error rate of a circuit. Explain." - ] - }, - { - "cell_type": "markdown", - "id": "cell-22", - "metadata": {}, - "source": [ - "## Exercises\n", - "\n", - "### Exercise 1: ZNE with Different Scale Factors and Extrapolators\n", - "\n", - "Try ZNE with more scale factors (1, 3, 5, 7, 9) and compare linear,\n", - "polynomial, and exponential extrapolation. Which extrapolator gets\n", - "closest to the ideal value?" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "cell-23", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above, fill in the ???, and run!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: ZNE with Different Scale Factors and Extrapolators\n", - "//\n", - "// Goal: Try ZNE with more scale factors (1, 3, 5, 7, 9) and compare\n", - "// linear, polynomial, and exponential extrapolation.\n", - "//\n", - "// TODO: Define the scale factors\n", - "// TODO: Run ZNE with each extrapolator and print a comparison table\n", - "//\n", - "// Skeleton:\n", - "\n", - "// Step 1: Define scale factors\n", - "// scaleFactors := []float64{1, 3, 5, 7, 9}\n", - "\n", - "// Step 2: Define extrapolators to compare\n", - "// extrapolators := []struct {\n", - "// name string\n", - "// ext mitigation.Extrapolator\n", - "// }{\n", - "// {\"Linear\", mitigation.LinearExtrapolator},\n", - "// {\"Polynomial\", mitigation.PolynomialExtrapolator},\n", - "// {\"Exponential\", mitigation.ExponentialExtrapolator},\n", - "// }\n", - "\n", - "// Step 3: Run ZNE with each extrapolator\n", - "// fmt.Printf(\"%-14s %10s %10s\\n\", \"Extrapolator\", \"Mitigated\", \"Error\")\n", - "// for _, ext := range extrapolators {\n", - "// result, err := mitigation.RunZNE(ctx, mitigation.ZNEConfig{\n", - "// Circuit: ???,\n", - "// Executor: ???,\n", - "// ScaleFactors: ???,\n", - "// Extrapolator: ???,\n", - "// })\n", - "// if err != nil {\n", - "// fmt.Printf(\"%-14s error: %v\\n\", ext.name, err)\n", - "// continue\n", - "// }\n", - "// fmt.Printf(\"%-14s %+10.4f %10.4f\\n\",\n", - "// ext.name, result.MitigatedValue, math.Abs(ideal-result.MitigatedValue))\n", - "// }\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above, fill in the ???, and run!\")" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "cell-24", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above, fill in the ???, and run!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: Combine ZNE with Pauli Twirling\n", - "//\n", - "// Goal: First twirl to convert coherent errors to Pauli noise, then\n", - "// apply ZNE on the twirled executor. Compare the combined result\n", - "// against ZNE-only, twirl-only, and the noisy baseline.\n", - "//\n", - "// TODO: Create a twirl+ZNE executor\n", - "// TODO: Run ZNE with the twirled executor\n", - "// TODO: Print a comparison of all approaches\n", - "//\n", - "// Skeleton:\n", - "\n", - "// Step 1: Create a \"twirl + ZNE\" executor\n", - "// The idea: wrap noisyExec in a twirling layer, then use that as the\n", - "// executor for ZNE.\n", - "//\n", - "// twirlZNEExec := func(ctx context.Context, circuit *ir.Circuit) (float64, error) {\n", - "// result, err := mitigation.RunTwirl(ctx, mitigation.TwirlConfig{\n", - "// Circuit: ???,\n", - "// Executor: ???,\n", - "// Samples: 50,\n", - "// })\n", - "// if err != nil {\n", - "// return 0, err\n", - "// }\n", - "// return result.MitigatedValue, nil\n", - "// }\n", - "\n", - "// Step 2: Run ZNE with the twirled executor\n", - "// combinedResult, err := mitigation.RunZNE(ctx, mitigation.ZNEConfig{\n", - "// Circuit: ???,\n", - "// Executor: ???,\n", - "// ScaleFactors: []float64{1, 3, 5},\n", - "// })\n", - "\n", - "// Step 3: Print comparison\n", - "// fmt.Println(\"Combining Pauli Twirling + ZNE:\")\n", - "// fmt.Printf(\" Ideal : %+.4f\\n\", ideal)\n", - "// fmt.Printf(\" Noisy : %+.4f (error: %.4f)\\n\", noisy, math.Abs(ideal-noisy))\n", - "// fmt.Printf(\" ZNE only: %+.4f (error: ???)\\n\", zneResult.MitigatedValue)\n", - "// fmt.Printf(\" Twirl only: %+.4f (error: ???)\\n\", twirlResult.MitigatedValue)\n", - "// fmt.Printf(\" Twirl + ZNE combined: %+.4f (error: ???)\\n\", combinedResult.MitigatedValue)\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above, fill in the ???, and run!\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-25", - "metadata": {}, - "source": [ - "## Key Takeaways\n", - "\n", - "1. **Error mitigation != error correction.** Mitigation uses classical\n", - " post-processing to improve noisy expectation values. It cannot fix\n", - " individual shots or replace fault-tolerant QEC, but it is practical\n", - " on today's NISQ devices.\n", - "\n", - "2. **ZNE** is the workhorse: amplify noise at several scale factors,\n", - " extrapolate to zero noise. No noise model required. Works best\n", - " when the expectation value decays smoothly with noise.\n", - "\n", - "3. **Readout mitigation** corrects classical measurement errors via a\n", - " calibrated confusion matrix. Full calibration costs $O(2^n)$; TREX\n", - " provides $O(n)$ scalability via randomized X insertions.\n", - "\n", - "4. **Dynamical decoupling** is preventive, not corrective: it inserts\n", - " refocusing pulses during idle periods to suppress decoherence.\n", - " Zero post-processing overhead.\n", - "\n", - "5. **Pauli twirling** converts coherent errors into stochastic Pauli\n", - " noise. This does not reduce the error rate, but makes the error\n", - " structure more predictable -- a prerequisite for PEC and beneficial\n", - " for ZNE.\n", - "\n", - "6. **PEC** is the only unbiased technique: it converges to the exact\n", - " ideal value with enough samples. The cost is exponential sampling\n", - " overhead $\\gamma^L$, limiting it to short, well-characterized circuits.\n", - "\n", - "7. **CDR** learns a noise correction model from near-Clifford training\n", - " circuits -- no noise model needed. Best suited for circuits with\n", - " non-Clifford gates where training data is informative.\n", - "\n", - "8. **Combining techniques** often outperforms any single method. A\n", - " common recipe: DD to protect idle qubits, twirling to convert\n", - " coherent errors, then ZNE or PEC on the stochastic residual." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/16-advanced-topics.ipynb b/notebooks/16-advanced-topics.ipynb deleted file mode 100644 index 09858a4..0000000 --- a/notebooks/16-advanced-topics.ipynb +++ /dev/null @@ -1,731 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a1b2c3d4", - "metadata": {}, - "source": [ - "# Notebook 16 -- Advanced Topics: Hamiltonian Simulation, HHL, and Beyond\n", - "\n", - "**Prerequisites:** Notebooks 01-08 and 11. Familiarity with QPE, Pauli operators, and noise.\n", - "\n", - "This capstone notebook surveys the advanced algorithmic features of the\n", - "Goqu SDK. We bring together concepts from across the curriculum to explore\n", - "**Hamiltonian simulation**, the **HHL algorithm** for linear systems,\n", - "**Clifford simulation** at massive scale, and **Pauli algebra**.\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Implement** Trotter-Suzuki Hamiltonian simulation and compare first vs second order.\n", - "2. **Describe** the HHL algorithm for solving linear systems.\n", - "3. **Demonstrate** Clifford simulation scaling to thousands of qubits.\n", - "4. **Use** Pauli algebra to multiply, commute, and compute expectation values.\n", - "\n", - "| Topic | Key idea |\n", - "|-------|----------|\n", - "| Trotter-Suzuki | Approximate $e^{-iHt}$ by slicing into small steps |\n", - "| HHL | Solve $Ax = b$ on a quantum computer |\n", - "| Clifford simulation | Stabilizer states up to 100,000 qubits |\n", - "| Pauli algebra | Multiply, commute, and compute expectation values |\n", - "\n", - "For pulse programming, operator representations, QASM/Quil interop, backends, and observability, see [Notebook 16b](16b-sdk-reference.ipynb)." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "b2c3d4e5", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"context\"\n", - "\t\"fmt\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/algorithm/hhl\"\n", - "\t\"github.com/splch/goqu/algorithm/trotter\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/sim/clifford\"\n", - "\t\"github.com/splch/goqu/sim/pauli\"\n", - "\t\"github.com/splch/goqu/sim/statevector\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "c3d4e5f6", - "metadata": {}, - "source": [ - "## Trotter-Suzuki Hamiltonian Simulation\n", - "\n", - "Many quantum algorithms require simulating the time evolution of a\n", - "physical system whose dynamics are governed by a Hamiltonian $H$. The\n", - "time-evolution operator is\n", - "\n", - "$$U(t) = e^{-iHt}$$\n", - "\n", - "When $H$ is a sum of non-commuting terms $H = \\sum_k H_k$, the matrix\n", - "exponential $e^{-i(H_1 + H_2 + \\cdots)t}$ cannot be factored exactly.\n", - "The **Trotter-Suzuki decomposition** approximates it by splitting into\n", - "small time steps:\n", - "\n", - "- **First-order (Lie-Trotter):**\n", - " $e^{-iHt} \\approx \\left[\\prod_k e^{-iH_k \\Delta t}\\right]^n$\n", - " where $\\Delta t = t/n$.\n", - "\n", - "- **Second-order (Suzuki-Trotter):**\n", - " $e^{-iHt} \\approx \\left[\\prod_k e^{-iH_k \\Delta t/2} \\cdot \\prod_{k'} e^{-iH_{k'} \\Delta t/2}\\right]^n$\n", - " where the second product runs in reverse order. This symmetric\n", - " splitting cancels the leading-order error term.\n", - "\n", - "Let's simulate time evolution of a 2-qubit transverse-field Heisenberg\n", - "Hamiltonian: $H = XX + YY + ZZ + 0.5 \\cdot ZI + 0.5 \\cdot IZ$.\n", - "The single-qubit $Z$ terms do not commute with the $XX$ and $YY$ terms,\n", - "ensuring non-trivial Trotter error." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "d4e5f6a7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Trotter circuit: 76 gates, depth 56\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "H\n", - "\n", - "\n", - "\n", - "RZ(0.5)\n", - "\n", - "\n", - "\n", - "H\n", - "H\n", - "RX(pi/2)\n", - "RX(pi/2)\n", - "\n", - "\n", - "\n", - "RZ(0.5)\n", - "\n", - "\n", - "\n", - "RX(-pi/2)\n", - "RX(-pi/2)\n", - "\n", - "\n", - "\n", - "RZ(0.5)\n", - "\n", - "\n", - "\n", - "RZ(0.25)\n", - "RZ(0.25)\n", - "H\n", - "H\n", - "\n", - "\n", - "\n", - "RZ(0.5)\n", - "\n", - "\n", - "\n", - "H\n", - "H\n", - "RX(pi/2)\n", - "RX(pi/2)\n", - "\n", - "\n", - "\n", - "RZ(0.5)\n", - "\n", - "\n", - "\n", - "RX(-pi/2)\n", - "RX(-pi/2)\n", - "\n", - "\n", - "\n", - "RZ(0.5)\n", - "\n", - "\n", - "\n", - "RZ(0.25)\n", - "RZ(0.25)\n", - "H\n", - "H\n", - "\n", - "\n", - "\n", - "RZ(0.5)\n", - "\n", - "\n", - "\n", - "H\n", - "H\n", - "RX(pi/2)\n", - "RX(pi/2)\n", - "\n", - "\n", - "\n", - "RZ(0.5)\n", - "\n", - "\n", - "\n", - "RX(-pi/2)\n", - "RX(-pi/2)\n", - "\n", - "\n", - "\n", - "RZ(0.5)\n", - "\n", - "\n", - "\n", - "RZ(0.25)\n", - "RZ(0.25)\n", - "H\n", - "H\n", - "\n", - "\n", - "\n", - "RZ(0.5)\n", - "\n", - "\n", - "\n", - "H\n", - "H\n", - "RX(pi/2)\n", - "RX(pi/2)\n", - "\n", - "\n", - "\n", - "RZ(0.5)\n", - "\n", - "\n", - "\n", - "RX(-pi/2)\n", - "RX(-pi/2)\n", - "\n", - "\n", - "\n", - "RZ(0.5)\n", - "\n", - "\n", - "\n", - "RZ(0.25)\n", - "RZ(0.25)\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// H = XX + YY + ZZ + 0.5*ZI + 0.5*IZ (transverse-field Heisenberg model)\n", - "// The single-qubit Z terms don't commute with XX and YY, creating real Trotter error.\n", - "xx, _ := pauli.Parse(\"XX\")\n", - "yy, _ := pauli.Parse(\"YY\")\n", - "zz, _ := pauli.Parse(\"ZZ\")\n", - "zi, _ := pauli.Parse(\"ZI\")\n", - "iz, _ := pauli.Parse(\"IZ\")\n", - "ham, _ := pauli.NewPauliSum([]pauli.PauliString{xx, yy, zz, zi.Scale(0.5), iz.Scale(0.5)})\n", - "\n", - "result, _ := trotter.Run(ctx, trotter.Config{\n", - "\tHamiltonian: ham,\n", - "\tTime: 1.0,\n", - "\tSteps: 4,\n", - "\tOrder: trotter.First,\n", - "})\n", - "\n", - "fmt.Printf(\"Trotter circuit: %d gates, depth %d\\n\",\n", - "\tresult.Circuit.Stats().GateCount, result.Circuit.Stats().Depth)\n", - "gonbui.DisplayHTML(draw.SVG(result.Circuit))" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "e5f6a7b8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "First-order: 76 gates, depth 56\n", - "Second-order: 152 gates, depth 112\n", - "\n", - "Fidelity vs high-precision reference:\n", - " First-order (4 steps): 1.00000000\n", - " Second-order (4 steps): 1.00000000\n", - "\n", - "Second-order Trotter is more accurate at the same step count.\n", - "The gap comes from non-commuting terms (XX vs ZI, YY vs IZ).\n" - ] - } - ], - "source": [ - "%%\n", - "// Compare first-order vs second-order Trotter decompositions.\n", - "// Using a Hamiltonian with non-commuting terms to see real error.\n", - "ctx := context.Background()\n", - "xx, _ := pauli.Parse(\"XX\")\n", - "yy, _ := pauli.Parse(\"YY\")\n", - "zz, _ := pauli.Parse(\"ZZ\")\n", - "zi, _ := pauli.Parse(\"ZI\")\n", - "iz, _ := pauli.Parse(\"IZ\")\n", - "ham, _ := pauli.NewPauliSum([]pauli.PauliString{xx, yy, zz, zi.Scale(0.5), iz.Scale(0.5)})\n", - "\n", - "r1, _ := trotter.Run(ctx, trotter.Config{\n", - "\tHamiltonian: ham, Time: 1.0, Steps: 4, Order: trotter.First,\n", - "})\n", - "r2, _ := trotter.Run(ctx, trotter.Config{\n", - "\tHamiltonian: ham, Time: 1.0, Steps: 4, Order: trotter.Second,\n", - "})\n", - "\n", - "fmt.Printf(\"First-order: %d gates, depth %d\\n\",\n", - "\tr1.Circuit.Stats().GateCount, r1.Circuit.Stats().Depth)\n", - "fmt.Printf(\"Second-order: %d gates, depth %d\\n\",\n", - "\tr2.Circuit.Stats().GateCount, r2.Circuit.Stats().Depth)\n", - "\n", - "// Use Evolve to get final statevectors and compare fidelity.\n", - "sv1, _ := trotter.Evolve(ctx, trotter.Config{\n", - "\tHamiltonian: ham, Time: 1.0, Steps: 4, Order: trotter.First,\n", - "}, nil)\n", - "sv2, _ := trotter.Evolve(ctx, trotter.Config{\n", - "\tHamiltonian: ham, Time: 1.0, Steps: 4, Order: trotter.Second,\n", - "}, nil)\n", - "\n", - "// High-precision reference (many steps).\n", - "svRef, _ := trotter.Evolve(ctx, trotter.Config{\n", - "\tHamiltonian: ham, Time: 1.0, Steps: 100, Order: trotter.Second,\n", - "}, nil)\n", - "\n", - "// Compute fidelity ||^2.\n", - "fidelity := func(a, b []complex128) float64 {\n", - "\tvar dot complex128\n", - "\tfor i := range a {\n", - "\t\tdot += complex(real(a[i]), -imag(a[i])) * b[i]\n", - "\t}\n", - "\treturn real(dot)*real(dot) + imag(dot)*imag(dot)\n", - "}\n", - "\n", - "fmt.Printf(\"\\nFidelity vs high-precision reference:\\n\")\n", - "fmt.Printf(\" First-order (4 steps): %.8f\\n\", fidelity(svRef, sv1))\n", - "fmt.Printf(\" Second-order (4 steps): %.8f\\n\", fidelity(svRef, sv2))\n", - "fmt.Println(\"\\nSecond-order Trotter is more accurate at the same step count.\")\n", - "fmt.Println(\"The gap comes from non-commuting terms (XX vs ZI, YY vs IZ).\")" - ] - }, - { - "cell_type": "markdown", - "id": "f6a7b8c9", - "metadata": {}, - "source": [ - "## HHL Algorithm for Linear Systems\n", - "\n", - "The **Harrow-Hassidim-Lloyd (HHL)** algorithm solves the linear system\n", - "$Ax = b$ by encoding the solution in the amplitudes of a quantum state.\n", - "For an $N \\times N$ Hermitian matrix $A$ with eigenvalues $\\lambda_j$\n", - "and eigenvectors $|u_j\\rangle$, if we decompose $|b\\rangle = \\sum_j\n", - "\\beta_j |u_j\\rangle$, then the solution is $|x\\rangle \\propto \\sum_j\n", - "(\\beta_j / \\lambda_j) |u_j\\rangle$.\n", - "\n", - "The algorithm uses three quantum registers:\n", - "\n", - "1. **Ancilla** (1 qubit) -- flags successful eigenvalue inversion.\n", - "2. **Phase register** -- stores eigenvalue estimates via QPE.\n", - "3. **System register** -- encodes $|b\\rangle$ and ultimately $|x\\rangle$.\n", - "\n", - "The success probability indicates how often post-selection succeeds." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "a7b8c9d0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "HHL success probability: 0.0337\n", - "Solution state vector: |0>: 0.8284+0.0000i |1>: 0.5601+-0.0000i \n", - "\n", - "HHL circuit: 5 qubits, 60 gates, depth 44\n" - ] - } - ], - "source": [ - "%%\n", - "ctx := context.Background()\n", - "\n", - "// Simple 2x2 system: A = 0.5*Z (diagonal Hamiltonian).\n", - "// Eigenvalues are +0.5 and -0.5.\n", - "// |b> = H|0> = |+> = (|0> + |1>) / sqrt(2)\n", - "zi, _ := pauli.Parse(\"Z\")\n", - "A, _ := pauli.NewPauliSum([]pauli.PauliString{zi.Scale(0.5)})\n", - "rhs, _ := builder.New(\"rhs\", 1).H(0).Build()\n", - "\n", - "result, err := hhl.Run(ctx, hhl.Config{\n", - "\tMatrix: A,\n", - "\tRHS: rhs,\n", - "\tNumPhaseBits: 3,\n", - "\tNumQubits: 1,\n", - "\tShots: 1000,\n", - "})\n", - "if err != nil {\n", - "\tfmt.Println(\"Error:\", err)\n", - "} else {\n", - "\tfmt.Printf(\"HHL success probability: %.4f\\n\", result.Success)\n", - "\tfmt.Printf(\"Solution state vector: \")\n", - "\tfor i, amp := range result.StateVector {\n", - "\t\tfmt.Printf(\"|%d>: %.4f+%.4fi \", i, real(amp), imag(amp))\n", - "\t}\n", - "\tfmt.Println()\n", - "\tfmt.Printf(\"\\nHHL circuit: %d qubits, %d gates, depth %d\\n\",\n", - "\t\tresult.Circuit.NumQubits(),\n", - "\t\tresult.Circuit.Stats().GateCount,\n", - "\t\tresult.Circuit.Stats().Depth)\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "b8c9d0e1", - "metadata": {}, - "source": [ - "## Clifford Simulation -- Stabilizer States at Scale\n", - "\n", - "Clifford circuits (composed of H, S, CNOT, and Pauli gates) can be\n", - "simulated efficiently on classical hardware using the **stabilizer\n", - "tableau** formalism. Where a full statevector simulator needs $2^n$\n", - "amplitudes, the Clifford simulator only needs $O(n^2)$ bits.\n", - "\n", - "This makes it possible to simulate circuits with **thousands or even\n", - "100,000 qubits** -- far beyond any statevector simulator. The catch:\n", - "only Clifford gates are allowed (no T gate, no arbitrary rotations).\n", - "\n", - "Let's create a 1000-qubit GHZ state: $|\\text{GHZ}\\rangle = (|0\\rangle^{\\otimes 1000} + |1\\rangle^{\\otimes 1000}) / \\sqrt{2}$." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "c9d0e1f2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1000-qubit GHZ state -- 2 distinct outcomes from 100 shots:\n", - " |111...111> (1000 qubits): 62 shots\n", - " |000...000> (1000 qubits): 38 shots\n", - "\n", - "A statevector simulator would need 2^1000 amplitudes for this.\n", - "The Clifford simulator handles it in milliseconds with O(n^2) memory.\n" - ] - } - ], - "source": [ - "%%\n", - "nQubits := 1000\n", - "b := builder.New(\"ghz-1000\", nQubits)\n", - "b.H(0)\n", - "for i := 0; i < nQubits-1; i++ {\n", - "\tb.CNOT(i, i+1)\n", - "}\n", - "b.MeasureAll()\n", - "c, err := b.Build()\n", - "if err != nil {\n", - "\tfmt.Println(\"Build error:\", err)\n", - "}\n", - "\n", - "sim := clifford.New(nQubits)\n", - "counts, err := sim.Run(c, 100)\n", - "if err != nil {\n", - "\tfmt.Println(\"Run error:\", err)\n", - "}\n", - "\n", - "// Should see only all-0s and all-1s.\n", - "fmt.Printf(\"1000-qubit GHZ state -- %d distinct outcomes from 100 shots:\\n\", len(counts))\n", - "for state, n := range counts {\n", - "\tfmt.Printf(\" |%s...%s> (%d qubits): %d shots\\n\",\n", - "\t\tstate[:3], state[len(state)-3:], len(state), n)\n", - "}\n", - "fmt.Println(\"\\nA statevector simulator would need 2^1000 amplitudes for this.\")\n", - "fmt.Println(\"The Clifford simulator handles it in milliseconds with O(n^2) memory.\")" - ] - }, - { - "cell_type": "markdown", - "id": "d0e1f2a3", - "metadata": {}, - "source": [ - "## Pauli Algebra\n", - "\n", - "The Pauli operators $\\{I, X, Y, Z\\}$ form a basis for all $2 \\times 2$\n", - "Hermitian matrices. Multi-qubit Pauli **strings** (tensor products like\n", - "$X \\otimes Z \\otimes I$) and their linear combinations (**Pauli sums**)\n", - "are the natural language for expressing Hamiltonians and observables.\n", - "\n", - "Key algebraic properties:\n", - "\n", - "- **Multiplication:** $X \\cdot Y = iZ$, and the product of two Pauli\n", - " strings is another Pauli string (up to a phase).\n", - "- **Commutation:** Two Pauli strings either commute or anticommute.\n", - " They commute iff they anticommute at an even number of qubit positions.\n", - "- **Expectation values:** $\\langle\\psi|P|\\psi\\rangle$ can be computed in\n", - " $O(2^n)$ time without materializing any matrix." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "e1f2a3b4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "XX * YY = (-1+0i)*ZZ\n", - "XX commutes with ZZ: true\n", - "XX commutes with YY: true\n", - "XY commutes with YX: true\n", - "\n", - "Expectation values for Bell state |Phi+>:\n", - " = 1.0000\n", - " = -1.0000\n", - " = 1.0000\n", - "\n", - " = 1.8000\n" - ] - } - ], - "source": [ - "%%\n", - "xx, _ := pauli.Parse(\"XX\")\n", - "yy, _ := pauli.Parse(\"YY\")\n", - "zz, _ := pauli.Parse(\"ZZ\")\n", - "\n", - "// Multiplication: XX * YY = ?\n", - "product := pauli.Mul(xx, yy)\n", - "fmt.Printf(\"XX * YY = %s\\n\", product)\n", - "\n", - "// Commutation relations.\n", - "fmt.Printf(\"XX commutes with ZZ: %v\\n\", pauli.Commutes(xx, zz))\n", - "fmt.Printf(\"XX commutes with YY: %v\\n\", pauli.Commutes(xx, yy))\n", - "fmt.Printf(\"XY commutes with YX: \")\n", - "xy, _ := pauli.Parse(\"XY\")\n", - "yx, _ := pauli.Parse(\"YX\")\n", - "fmt.Printf(\"%v\\n\", pauli.Commutes(xy, yx))\n", - "\n", - "// Expectation values on a Bell state.\n", - "sv := statevector.New(2)\n", - "bellC, _ := builder.New(\"bell\", 2).H(0).CNOT(0, 1).Build()\n", - "sv.Evolve(bellC)\n", - "\n", - "fmt.Printf(\"\\nExpectation values for Bell state |Phi+>:\\n\")\n", - "fmt.Printf(\" = %.4f\\n\", sv.ExpectPauliString(xx))\n", - "fmt.Printf(\" = %.4f\\n\", sv.ExpectPauliString(yy))\n", - "fmt.Printf(\" = %.4f\\n\", sv.ExpectPauliString(zz))\n", - "\n", - "// PauliSum expectation (Hamiltonian).\n", - "ham, _ := pauli.NewPauliSum([]pauli.PauliString{\n", - "\txx,\n", - "\tyy.Scale(-0.5),\n", - "\tzz.Scale(0.3),\n", - "})\n", - "fmt.Printf(\"\\n = %.4f\\n\", sv.ExpectPauliSum(ham))" - ] - }, - { - "cell_type": "markdown", - "id": "6r95g3wmny2", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. Why does second-order Trotterization give better accuracy than first-order at the same number of steps?\n", - "2. Why can Clifford circuits be simulated efficiently on classical computers, and what gate breaks this efficiency?\n", - "3. How does HHL encode the solution to a linear system in quantum amplitudes, and why is post-selection needed?" - ] - }, - { - "cell_type": "markdown", - "id": "e3f4a5b6", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## Exercises\n", - "\n", - "### Exercise 1 -- Trotter Accuracy Comparison\n", - "\n", - "Sweep the number of Trotter steps from 1 to 16 for both first-order\n", - "and second-order decompositions of the transverse-field Heisenberg\n", - "Hamiltonian $H = XX + YY + ZZ + 0.5 \\cdot ZI + 0.5 \\cdot IZ$\n", - "(the same Hamiltonian from the example above). Print the fidelity vs\n", - "step count to see how second-order converges faster." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "f4a5b6c7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above, fill in the ???, and run!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: Trotter Accuracy Comparison\n", - "//\n", - "// Goal: Sweep Trotter steps from 1 to 16 for both first-order and\n", - "// second-order decompositions and compare fidelity vs a reference.\n", - "//\n", - "// Hamiltonian: H = XX + YY + ZZ + 0.5*ZI + 0.5*IZ (transverse-field Heisenberg)\n", - "//\n", - "// TODO: Build the Hamiltonian\n", - "// TODO: Compute a high-precision reference state\n", - "// TODO: Sweep step counts and print fidelity for both orders\n", - "//\n", - "// Skeleton:\n", - "\n", - "// ctx := context.Background()\n", - "\n", - "// Step 1: Build the Hamiltonian\n", - "// xx, _ := pauli.Parse(\"XX\")\n", - "// yy, _ := pauli.Parse(\"YY\")\n", - "// zz, _ := pauli.Parse(\"ZZ\")\n", - "// zi, _ := pauli.Parse(\"ZI\")\n", - "// iz, _ := pauli.Parse(\"IZ\")\n", - "// ham, _ := pauli.NewPauliSum([]pauli.PauliString{xx, yy, zz, zi.Scale(0.5), iz.Scale(0.5)})\n", - "\n", - "// Step 2: Compute a high-precision reference (many steps, second-order)\n", - "// svRef, _ := trotter.Evolve(ctx, trotter.Config{\n", - "// Hamiltonian: ham, Time: 1.0, Steps: 200, Order: trotter.Second,\n", - "// }, nil)\n", - "\n", - "// Step 3: Define a fidelity function\n", - "// fidelity := func(a, b []complex128) float64 {\n", - "// var dot complex128\n", - "// for i := range a {\n", - "// dot += complex(real(a[i]), -imag(a[i])) * b[i]\n", - "// }\n", - "// return real(dot)*real(dot) + imag(dot)*imag(dot)\n", - "// }\n", - "\n", - "// Step 4: Sweep step counts and compare\n", - "// fmt.Printf(\"%6s %16s %16s\\n\", \"Steps\", \"1st-order F\", \"2nd-order F\")\n", - "// for _, steps := range []int{1, 2, 4, 8, 16} {\n", - "// sv1, _ := trotter.Evolve(ctx, trotter.Config{\n", - "// Hamiltonian: ham, Time: 1.0, Steps: steps, Order: trotter.First,\n", - "// }, nil)\n", - "// sv2, _ := trotter.Evolve(ctx, trotter.Config{\n", - "// Hamiltonian: ham, Time: 1.0, Steps: steps, Order: trotter.Second,\n", - "// }, nil)\n", - "// fmt.Printf(\"%6d %16.10f %16.10f\\n\", steps, fidelity(svRef, sv1), fidelity(svRef, sv2))\n", - "// }\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above, fill in the ???, and run!\")" - ] - }, - { - "cell_type": "markdown", - "id": "c7d8e9f0", - "metadata": {}, - "source": [ - "## Key Takeaways -- Curriculum Conclusion\n", - "\n", - "Over sixteen notebooks, we have built a comprehensive understanding of\n", - "quantum computing using the Goqu SDK:\n", - "\n", - "1. **Hamiltonian simulation** via Trotter-Suzuki decomposition lets us\n", - " approximate $e^{-iHt}$ for arbitrary Hamiltonians. Second-order\n", - " Trotter converges faster than first-order at the same circuit depth.\n", - "\n", - "2. **HHL** solves linear systems $Ax = b$ by encoding the solution in\n", - " quantum amplitudes. It requires QPE, controlled rotations, and\n", - " post-selection.\n", - "\n", - "3. **Clifford simulation** using stabilizer tableaux can handle up to\n", - " 100,000 qubits in polynomial time -- but only for Clifford circuits\n", - " (H, S, CNOT, Pauli gates).\n", - "\n", - "4. **Pauli algebra** provides the mathematical foundation for expressing\n", - " Hamiltonians, computing expectation values, and analyzing commutation\n", - " relations -- all in $O(2^n)$ without materializing matrices.\n", - "\n", - "For pulse-level programming, operator representations, QASM/Quil interop,\n", - "backends, and observability hooks, see [Notebook 16b](16b-sdk-reference.ipynb).\n", - "\n", - "---\n", - "\n", - "**Congratulations on completing the quantum computing curriculum!**\n", - "You now have the tools and understanding to build, simulate, transpile,\n", - "and run quantum algorithms -- from single-qubit gates all the way to\n", - "production-grade Hamiltonian simulation.\n", - "\n", - "The quantum advantage is not in any single algorithm, but in the ability\n", - "to compose all of these pieces: express a problem as a Hamiltonian,\n", - "simulate it with the right decomposition, transpile to hardware\n", - "constraints, mitigate noise, and monitor the entire pipeline." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/16b-sdk-reference.ipynb b/notebooks/16b-sdk-reference.ipynb deleted file mode 100644 index d3b5add..0000000 --- a/notebooks/16b-sdk-reference.ipynb +++ /dev/null @@ -1,847 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "sdk-intro-md", - "metadata": {}, - "source": [ - "# Notebook 16b -- SDK Reference: Pulse, Operators, QASM, and Backends\n", - "\n", - "**Prerequisites:** Notebooks 01-11. Familiarity with gates, noise, and circuit construction.\n", - "\n", - "Notebook 16 covered the algorithmic frontier: Hamiltonian simulation,\n", - "HHL, Clifford simulation, and Pauli algebra. This companion notebook\n", - "completes the curriculum by surveying the **SDK-level capabilities**\n", - "that connect algorithms to real hardware and production workflows:\n", - "**pulse-level programming**, **operator representations**, **QASM/Quil\n", - "interop**, **backends**, and **observability hooks**.\n", - "\n", - "By the end of this notebook you will be able to:\n", - "\n", - "1. **Program** pulse-level waveforms below the gate abstraction.\n", - "2. **Convert** between operator representations (Kraus, SuperOp, Choi, PTM).\n", - "3. **Emit and parse** circuits in OpenQASM 3.0 and Quil formats.\n", - "4. **Submit** circuits to local, mock, and cloud backends through a unified interface.\n", - "5. **Attach** observability hooks for production-grade monitoring.\n", - "\n", - "### What we will cover\n", - "\n", - "| Topic | Key idea |\n", - "|-------|----------|\n", - "| Pulse-level control | Program waveforms below the gate abstraction |\n", - "| Operator representations | Kraus, SuperOp, Choi, PTM -- and conversions |\n", - "| QASM / Quil interop | Emit and parse standard circuit formats |\n", - "| Backends | Local simulator, mock backend, cloud targets |\n", - "| Observability | Hook into simulation and transpilation events |" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "sdk-imports", - "metadata": {}, - "outputs": [], - "source": [ - "import (\n", - "\t\"context\"\n", - "\t\"fmt\"\n", - "\t\"math\"\n", - "\n", - "\t\"github.com/janpfeifer/gonb/gonbui\"\n", - "\t\"github.com/splch/goqu/backend\"\n", - "\t\"github.com/splch/goqu/backend/local\"\n", - "\t\"github.com/splch/goqu/backend/mock\"\n", - "\t\"github.com/splch/goqu/circuit/builder\"\n", - "\t\"github.com/splch/goqu/circuit/draw\"\n", - "\t\"github.com/splch/goqu/observe\"\n", - "\t\"github.com/splch/goqu/pulse\"\n", - "\t\"github.com/splch/goqu/pulse/waveform\"\n", - "\tqasmEmitter \"github.com/splch/goqu/qasm/emitter\"\n", - "\t\"github.com/splch/goqu/qasm/parser\"\n", - "\tquilEmitter \"github.com/splch/goqu/quil/emitter\"\n", - "\t\"github.com/splch/goqu/sim/noise\"\n", - "\t\"github.com/splch/goqu/sim/operator\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "sdk-pulse-md", - "metadata": {}, - "source": [ - "## Pulse-Level Programming\n", - "\n", - "Gates are an abstraction. At the hardware level, quantum operations are\n", - "implemented by shaped microwave, laser, or RF **pulses** applied to\n", - "control lines (ports). Goqu's pulse package lets you program at this\n", - "lower level:\n", - "\n", - "- **Port** -- a hardware I/O endpoint with a time resolution $dt$.\n", - "- **Frame** -- a software reference clock attached to a port, with a\n", - " carrier frequency and phase.\n", - "- **Waveform** -- the pulse envelope (Gaussian, DRAG, constant, etc.).\n", - "- **Instructions** -- Play, Delay, ShiftPhase, SetFrequency, Capture, etc.\n", - "\n", - "This is analogous to OpenPulse in Qiskit or calibration-level access\n", - "on IBM hardware." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "sdk-pulse-code", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Pulse program: 1 ports, 1 frames, 5 instructions\n", - "Total duration: 1420.00 ns\n", - "Program(x-pulse: 1 ports, 1 frames, 5 instructions, 1.42e-06 s)\n" - ] - } - ], - "source": [ - "%%\n", - "// Define a drive port with dt = 0.2222 ns (typical for IBM hardware).\n", - "port := pulse.MustPort(\"d0\", 2.222e-10)\n", - "frame := pulse.MustFrame(\"d0_frame\", port, 5.0e9, 0.0)\n", - "\n", - "// Build a Gaussian X pulse followed by a phase-shifted pulse.\n", - "gaussian := waveform.MustGaussian(0.5, 160e-9, 40e-9)\n", - "\n", - "prog, err := pulse.NewBuilder(\"x-pulse\").\n", - "\tAddPort(port).\n", - "\tAddFrame(frame).\n", - "\tPlay(frame, gaussian).\n", - "\tDelay(frame, 100e-9).\n", - "\tShiftPhase(frame, math.Pi/2).\n", - "\tPlay(frame, gaussian).\n", - "\tCapture(frame, 1000e-9).\n", - "\tBuild()\n", - "\n", - "if err != nil {\n", - "\tfmt.Println(\"Error:\", err)\n", - "} else {\n", - "\tstats := prog.Stats()\n", - "\tfmt.Printf(\"Pulse program: %d ports, %d frames, %d instructions\\n\",\n", - "\t\tstats.NumPorts, stats.NumFrames, stats.NumInstructions)\n", - "\tfmt.Printf(\"Total duration: %.2f ns\\n\", stats.TotalDuration*1e9)\n", - "\tfmt.Println(prog)\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "sdk-operator-md", - "metadata": {}, - "source": [ - "## Operator Representations\n", - "\n", - "A quantum channel can be described in four equivalent ways, each useful\n", - "for different purposes:\n", - "\n", - "| Representation | Definition | Use case |\n", - "|:--|:--|:--|\n", - "| **Kraus** | $\\mathcal{E}(\\rho) = \\sum_k E_k \\rho E_k^\\dagger$ | Simulation, physical intuition |\n", - "| **SuperOp** | $\\text{vec}(\\mathcal{E}(\\rho)) = S \\cdot \\text{vec}(\\rho)$ | Composition (matrix multiply) |\n", - "| **Choi** | $\\Lambda = (\\mathcal{I} \\otimes \\mathcal{E})(|\\Omega\\rangle\\langle\\Omega|)$ | CP checks (positive semidefinite?) |\n", - "| **PTM** | $R_{ij} = \\text{Tr}(\\sigma_i \\cdot \\mathcal{E}(\\sigma_j)) / d$ | Pauli error analysis |\n", - "\n", - "Goqu provides conversion functions between all representations." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "sdk-operator-code", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Depolarizing(p=0.1) representations:\n", - " Kraus operators: 4\n", - " SuperOp matrix: 16 elements (4x4)\n", - " Choi matrix: 16 elements (4x4)\n", - " PTM matrix: 16 elements (4x4)\n", - "\n", - "Fidelity measures:\n", - " Process fidelity: 0.9000\n", - " Average gate fidelity: 0.9333\n", - "\n", - "Physicality check:\n", - " Is CPTP: true\n", - "\n", - "Round-trip (Kraus -> Choi -> Kraus):\n", - " Original process fidelity: 0.900000\n", - " Round-trip process fidelity: 0.900000\n" - ] - } - ], - "source": [ - "%%\n", - "// Start from a depolarizing noise channel and convert between representations.\n", - "ch := noise.Depolarizing1Q(0.1)\n", - "kraus := operator.FromChannel(ch)\n", - "\n", - "superOp := operator.KrausToSuperOp(kraus)\n", - "choi := operator.KrausToChoi(kraus)\n", - "ptm := operator.KrausToPTM(kraus)\n", - "\n", - "fmt.Printf(\"Depolarizing(p=0.1) representations:\\n\")\n", - "fmt.Printf(\" Kraus operators: %d\\n\", len(kraus.Operators()))\n", - "fmt.Printf(\" SuperOp matrix: %d elements (4x4)\\n\", len(superOp.Matrix()))\n", - "fmt.Printf(\" Choi matrix: %d elements (4x4)\\n\", len(choi.Matrix()))\n", - "fmt.Printf(\" PTM matrix: %d elements (4x4)\\n\", len(ptm.Matrix()))\n", - "\n", - "// Fidelity measures.\n", - "fmt.Printf(\"\\nFidelity measures:\\n\")\n", - "fmt.Printf(\" Process fidelity: %.4f\\n\", operator.ProcessFidelity(kraus))\n", - "fmt.Printf(\" Average gate fidelity: %.4f\\n\", operator.AverageGateFidelity(kraus))\n", - "\n", - "// CPTP validation.\n", - "fmt.Printf(\"\\nPhysicality check:\\n\")\n", - "fmt.Printf(\" Is CPTP: %v\\n\", operator.IsCPTP(kraus, 1e-10))\n", - "\n", - "// Round-trip: Kraus -> Choi -> Kraus and verify fidelity is preserved.\n", - "roundTrip := operator.ChoiToKraus(choi)\n", - "fmt.Printf(\"\\nRound-trip (Kraus -> Choi -> Kraus):\\n\")\n", - "fmt.Printf(\" Original process fidelity: %.6f\\n\", operator.ProcessFidelity(kraus))\n", - "fmt.Printf(\" Round-trip process fidelity: %.6f\\n\", operator.ProcessFidelity(roundTrip))" - ] - }, - { - "cell_type": "markdown", - "id": "sdk-qasm-md", - "metadata": {}, - "source": [ - "## QASM and Quil Interop\n", - "\n", - "Quantum circuits need to be exchanged between tools, compilers, and\n", - "hardware vendors. Goqu supports two standard formats:\n", - "\n", - "- **OpenQASM 3.0** -- the IBM-originated standard used across the\n", - " industry. `qasmEmitter.EmitString` produces QASM, and\n", - " `parser.ParseString` reads it back.\n", - "\n", - "- **Quil** -- Rigetti's instruction set. `quilEmitter.EmitString`\n", - " produces Quil source.\n", - "\n", - "Let's round-trip a circuit through OpenQASM 3.0 and also emit Quil." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "sdk-qasm-code", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Original circuit:\n", - "OpenQASM 3.0:\n", - "OPENQASM 3.0;\n", - "include \"stdgates.inc\";\n", - "qubit[2] q;\n", - "bit[2] c;\n", - "\n", - "h q[0];\n", - "cx q[0], q[1];\n", - "c[0] = measure q[0];\n", - "c[1] = measure q[1];\n", - "\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "q0\n", - "\n", - "q1\n", - "\n", - "H\n", - "\n", - "\n", - "\n", - "M\n", - "M\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Parsed back from QASM:\n" - ] - } - ], - "source": [ - "%%\n", - "c, _ := builder.New(\"qasm-demo\", 2).H(0).CNOT(0, 1).MeasureAll().Build()\n", - "\n", - "fmt.Println(\"Original circuit:\")\n", - "gonbui.DisplayHTML(draw.SVG(c))\n", - "\n", - "// Emit to OpenQASM 3.0.\n", - "qasmStr, err := qasmEmitter.EmitString(c)\n", - "if err != nil {\n", - "\tfmt.Println(\"QASM emit error:\", err)\n", - "} else {\n", - "\tfmt.Println(\"OpenQASM 3.0:\")\n", - "\tfmt.Println(qasmStr)\n", - "}\n", - "\n", - "// Parse back from QASM.\n", - "parsed, err := parser.ParseString(qasmStr)\n", - "if err != nil {\n", - "\tfmt.Println(\"Parse error:\", err)\n", - "} else {\n", - "\tfmt.Println(\"Parsed back from QASM:\")\n", - "\tgonbui.DisplayHTML(draw.SVG(parsed))\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "sdk-quil-code", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Quil:\n", - "DECLARE ro BIT[2]\n", - "\n", - "H 0\n", - "CNOT 0 1\n", - "MEASURE 0 ro[0]\n", - "MEASURE 1 ro[1]\n", - "\n" - ] - } - ], - "source": [ - "%%\n", - "// Emit the same circuit to Quil (Rigetti format).\n", - "c, _ := builder.New(\"quil-demo\", 2).H(0).CNOT(0, 1).MeasureAll().Build()\n", - "\n", - "quilStr, err := quilEmitter.EmitString(c)\n", - "if err != nil {\n", - "\tfmt.Println(\"Quil emit error:\", err)\n", - "} else {\n", - "\tfmt.Println(\"Quil:\")\n", - "\tfmt.Println(quilStr)\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "sdk-backends-md", - "metadata": {}, - "source": [ - "## Backends\n", - "\n", - "The `backend.Backend` interface abstracts over execution targets:\n", - "\n", - "```go\n", - "type Backend interface {\n", - " Name() string\n", - " Target() target.Target\n", - " Submit(ctx, req) (*Job, error)\n", - " Status(ctx, jobID) (*JobStatus, error)\n", - " Result(ctx, jobID) (*Result, error)\n", - " Cancel(ctx, jobID) error\n", - "}\n", - "```\n", - "\n", - "Goqu ships with:\n", - "\n", - "- **`local.Backend`** -- runs circuits on the local statevector simulator.\n", - "- **`mock.Backend`** -- a configurable test double for job pipelines.\n", - "- Cloud backends for IBM, Google, IonQ, Quantinuum, Rigetti, and AWS Braket.\n", - "\n", - "All backends share the same submit/status/result lifecycle, so code\n", - "written against one backend works with any other." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "sdk-backends-code", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Local backend: \"local.simulator\"\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2026/03/17 12:35:21 INFO simulating circuit qubits=2 gates=4 shots=1000\n", - "2026/03/17 12:35:21 INFO simulation complete qubits=2 elapsed=47.083µs\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Job submitted: ID=d905cf72eee06e4bac0ccaf90ad3a778, State=completed\n", - "Results (1000 shots):\n", - " |00>: 505\n", - " |11>: 495\n", - "\n", - "Mock backend: \"test-backend\"\n" - ] - } - ], - "source": [ - "%%\n", - "// Local backend: runs circuits on the local statevector simulator.\n", - "lb := local.New()\n", - "fmt.Printf(\"Local backend: %q\\n\", lb.Name())\n", - "\n", - "c, _ := builder.New(\"bell\", 2).H(0).CNOT(0, 1).MeasureAll().Build()\n", - "ctx := context.Background()\n", - "\n", - "job, err := lb.Submit(ctx, &backend.SubmitRequest{\n", - "\tCircuit: c,\n", - "\tShots: 1000,\n", - "\tName: \"bell-test\",\n", - "})\n", - "if err != nil {\n", - "\tfmt.Println(\"Submit error:\", err)\n", - "} else {\n", - "\tfmt.Printf(\"Job submitted: ID=%s, State=%s\\n\", job.ID, job.State)\n", - "\n", - "\tres, err := lb.Result(ctx, job.ID)\n", - "\tif err != nil {\n", - "\t\tfmt.Println(\"Result error:\", err)\n", - "\t} else {\n", - "\t\tfmt.Printf(\"Results (%d shots):\\n\", res.Shots)\n", - "\t\tfor bitstring, count := range res.ToCounts() {\n", - "\t\t\tfmt.Printf(\" |%s>: %d\\n\", bitstring, count)\n", - "\t\t}\n", - "\t}\n", - "}\n", - "\n", - "// Mock backend: configurable test double.\n", - "mb := mock.New(\"test-backend\")\n", - "fmt.Printf(\"\\nMock backend: %q\\n\", mb.Name())" - ] - }, - { - "cell_type": "markdown", - "id": "sdk-observe-md", - "metadata": {}, - "source": [ - "## Observability Hooks\n", - "\n", - "When running quantum workloads in production, you need visibility into\n", - "what is happening: how long simulations take, how deep transpiled\n", - "circuits are, how many backend calls are made. Goqu's `observe` package\n", - "provides **zero-dependency hooks** that you attach to a `context.Context`.\n", - "\n", - "Available hooks:\n", - "\n", - "- `WrapSim` -- called around simulation execution.\n", - "- `WrapTranspile` -- called around the transpilation pipeline.\n", - "- `WrapPass` -- called around each individual transpilation pass.\n", - "- `WrapJob` -- called around job submission through result retrieval.\n", - "- `WrapHTTP` -- called around backend HTTP requests.\n", - "- `WrapSweep` -- called around parameter sweep execution.\n", - "- `OnJobPoll` -- called each time a job is polled for status.\n", - "\n", - "This integrates naturally with OpenTelemetry or Prometheus via the\n", - "`otelbridge` and `prombridge` sub-packages." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "sdk-observe-code", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[observe] Simulating 2-qubit circuit (4 gates, 100 shots)...\n", - "[observe] Simulation complete!\n", - "\n", - "Got 100 shots with hooks active.\n", - "\n", - "Hooks are zero-cost when not attached (nil check).\n", - "In production, wire them to OpenTelemetry for distributed tracing.\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2026/03/17 12:35:24 INFO simulating circuit qubits=2 gates=4 shots=100\n", - "2026/03/17 12:35:24 INFO simulation complete qubits=2 elapsed=43.5µs\n" - ] - } - ], - "source": [ - "%%\n", - "hooks := &observe.Hooks{\n", - "\tWrapSim: func(ctx context.Context, info observe.SimInfo) (context.Context, func(err error)) {\n", - "\t\tfmt.Printf(\"[observe] Simulating %d-qubit circuit (%d gates, %d shots)...\\n\",\n", - "\t\t\tinfo.NumQubits, info.GateCount, info.Shots)\n", - "\t\treturn ctx, func(err error) {\n", - "\t\t\tif err == nil {\n", - "\t\t\t\tfmt.Println(\"[observe] Simulation complete!\")\n", - "\t\t\t} else {\n", - "\t\t\t\tfmt.Printf(\"[observe] Simulation failed: %v\\n\", err)\n", - "\t\t\t}\n", - "\t\t}\n", - "\t},\n", - "}\n", - "\n", - "ctx := observe.WithHooks(context.Background(), hooks)\n", - "\n", - "// Now any simulation run with this context will trigger the hook.\n", - "// The local backend respects observe hooks.\n", - "lb := local.New()\n", - "c, _ := builder.New(\"observed\", 2).H(0).CNOT(0, 1).MeasureAll().Build()\n", - "job, err := lb.Submit(ctx, &backend.SubmitRequest{Circuit: c, Shots: 100})\n", - "if err != nil {\n", - "\tfmt.Println(\"Error:\", err)\n", - "} else {\n", - "\tres, _ := lb.Result(ctx, job.ID)\n", - "\tfmt.Printf(\"\\nGot %d shots with hooks active.\\n\", res.Shots)\n", - "}\n", - "\n", - "fmt.Println(\"\\nHooks are zero-cost when not attached (nil check).\")\n", - "fmt.Println(\"In production, wire them to OpenTelemetry for distributed tracing.\")" - ] - }, - { - "cell_type": "markdown", - "id": "mmond2a16oo", - "metadata": {}, - "source": [ - "## Self-Check Questions\n", - "\n", - "Before attempting the exercises, make sure you can answer these:\n", - "\n", - "1. Why is a virtual Z-rotation \"free\" in pulse-level programming while a physical Y-rotation costs gate time?\n", - "2. What property must a set of Kraus operators satisfy to represent a valid (physical) quantum channel?\n", - "3. Why is the QASM round-trip test important for verifying that circuit semantics are preserved across tools?" - ] - }, - { - "cell_type": "markdown", - "id": "sdk-exercises-md", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## Exercises\n", - "\n", - "### Exercise 1 -- Custom Pulse Sequence for a Hadamard Gate\n", - "\n", - "On superconducting hardware, the Hadamard gate $H = (X + Z)/\\sqrt{2}$\n", - "can be decomposed as $H = RZ(\\pi) \\cdot RY(\\pi/2)$. Build a pulse\n", - "sequence that implements this: a Y-rotation pulse (Gaussian on the\n", - "drive frame) followed by a Z-rotation (a virtual phase shift, which is\n", - "free on hardware)." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "sdk-ex1-code", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above, fill in the ???, and run!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 1: Custom Pulse Sequence for a Hadamard Gate\n", - "//\n", - "// Goal: Build a pulse program that implements H = RZ(pi) . RY(pi/2).\n", - "// RY is a physical Gaussian pulse; RZ is a virtual phase shift.\n", - "//\n", - "// TODO: Create a drive port and frame\n", - "// TODO: Build the pulse program with Play (for RY) and ShiftPhase (for RZ)\n", - "//\n", - "// Skeleton:\n", - "\n", - "// Step 1: Define hardware port and frame\n", - "// port := pulse.MustPort(\"d0\", 2.222e-10)\n", - "// frame := pulse.MustFrame(\"d0_frame\", port, 5.0e9, 0.0)\n", - "\n", - "// Step 2: Create the RY(pi/2) Gaussian pulse\n", - "// The amplitude is calibrated so the pulse area equals pi/2.\n", - "// ryPulse := waveform.MustGaussian(???, 160e-9, 40e-9)\n", - "\n", - "// Step 3: Build the pulse program\n", - "// hadamardProg, err := pulse.NewBuilder(\"hadamard-pulse\").\n", - "// AddPort(???).\n", - "// AddFrame(???).\n", - "// Play(frame, ???). // RY(pi/2) via physical pulse\n", - "// ShiftPhase(frame, ???). // RZ(pi) via virtual phase shift\n", - "// Build()\n", - "//\n", - "// if err != nil {\n", - "// fmt.Println(\"Error:\", err)\n", - "// } else {\n", - "// stats := hadamardProg.Stats()\n", - "// fmt.Printf(\"Hadamard pulse program:\\n\")\n", - "// fmt.Printf(\" Instructions: %d\\n\", stats.NumInstructions)\n", - "// fmt.Printf(\" Total duration: %.1f ns\\n\", stats.TotalDuration*1e9)\n", - "// fmt.Println(hadamardProg)\n", - "// }\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above, fill in the ???, and run!\")" - ] - }, - { - "cell_type": "markdown", - "id": "sdk-ex2-md", - "metadata": {}, - "source": [ - "### Exercise 2 -- QASM Round-Trip with a 3-Qubit Circuit\n", - "\n", - "Build a 3-qubit GHZ circuit ($H(0) \\to CNOT(0,1) \\to CNOT(1,2)$),\n", - "emit it to OpenQASM 3.0, parse it back, and verify that the parsed\n", - "circuit has the same gate count and qubit count as the original." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "sdk-ex2-code", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above and run!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 2: QASM Round-Trip with a 3-Qubit Circuit\n", - "//\n", - "// Goal: Build a 3-qubit GHZ circuit, emit to QASM, parse back,\n", - "// and verify structural equivalence.\n", - "//\n", - "// TODO: Build the GHZ circuit\n", - "// TODO: Emit to QASM and parse back\n", - "// TODO: Compare gate counts\n", - "//\n", - "// Skeleton:\n", - "\n", - "// Step 1: Build the circuit\n", - "// c, _ := builder.New(\"ghz3\", 3).H(0).CNOT(0, 1).CNOT(1, 2).MeasureAll().Build()\n", - "\n", - "// Step 2: Emit to QASM\n", - "// qasmStr, err := qasmEmitter.EmitString(c)\n", - "// if err != nil {\n", - "// fmt.Println(\"Emit error:\", err)\n", - "// }\n", - "// fmt.Println(\"QASM output:\")\n", - "// fmt.Println(qasmStr)\n", - "\n", - "// Step 3: Parse back and compare\n", - "// parsed, err := parser.ParseString(qasmStr)\n", - "// if err != nil {\n", - "// fmt.Println(\"Parse error:\", err)\n", - "// }\n", - "// fmt.Printf(\"Original: %d qubits, %d gates\\n\", c.NumQubits(), c.Stats().GateCount)\n", - "// fmt.Printf(\"Parsed: %d qubits, %d gates\\n\", parsed.NumQubits(), parsed.Stats().GateCount)\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above and run!\")" - ] - }, - { - "cell_type": "markdown", - "id": "sdk-ex3-md", - "metadata": {}, - "source": [ - "### Exercise 3 -- Backend Job Pipeline\n", - "\n", - "Submit a 3-qubit circuit to the local backend and the mock backend.\n", - "Compare the results. The mock backend returns uniform random counts,\n", - "while the local backend produces physically correct distributions." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "sdk-ex3-code", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TODO: Uncomment the code above and run!\n" - ] - } - ], - "source": [ - "%%\n", - "// Exercise 3: Backend Job Pipeline\n", - "//\n", - "// Goal: Submit the same circuit to local and mock backends,\n", - "// compare the result distributions.\n", - "//\n", - "// TODO: Build a 3-qubit GHZ circuit\n", - "// TODO: Submit to local backend and collect results\n", - "// TODO: Submit to mock backend and collect results\n", - "// TODO: Print both distributions side by side\n", - "//\n", - "// Skeleton:\n", - "\n", - "// ctx := context.Background()\n", - "// c, _ := builder.New(\"ghz3\", 3).H(0).CNOT(0, 1).CNOT(1, 2).MeasureAll().Build()\n", - "\n", - "// Step 1: Local backend\n", - "// lb := local.New()\n", - "// job1, _ := lb.Submit(ctx, &backend.SubmitRequest{Circuit: c, Shots: 1000})\n", - "// res1, _ := lb.Result(ctx, job1.ID)\n", - "// fmt.Println(\"Local backend results:\")\n", - "// for bs, count := range res1.ToCounts() {\n", - "// fmt.Printf(\" |%s>: %d\\n\", bs, count)\n", - "// }\n", - "\n", - "// Step 2: Mock backend\n", - "// mb := mock.New(\"test\")\n", - "// job2, _ := mb.Submit(ctx, &backend.SubmitRequest{Circuit: c, Shots: 1000})\n", - "// res2, _ := mb.Result(ctx, job2.ID)\n", - "// fmt.Println(\"\\nMock backend results:\")\n", - "// for bs, count := range res2.ToCounts() {\n", - "// fmt.Printf(\" |%s>: %d\\n\", bs, count)\n", - "// }\n", - "\n", - "fmt.Println(\"TODO: Uncomment the code above and run!\")" - ] - }, - { - "cell_type": "markdown", - "id": "sdk-takeaways-md", - "metadata": {}, - "source": [ - "## Key Takeaways -- Curriculum Conclusion\n", - "\n", - "Over sixteen notebooks (plus this SDK reference), we have built a\n", - "comprehensive understanding of quantum computing using the Goqu SDK.\n", - "\n", - "From Notebook 16 (advanced algorithms):\n", - "\n", - "1. **Hamiltonian simulation** via Trotter-Suzuki decomposition lets us\n", - " approximate $e^{-iHt}$ for arbitrary Hamiltonians.\n", - "2. **HHL** solves linear systems $Ax = b$ by encoding the solution in\n", - " quantum amplitudes.\n", - "3. **Clifford simulation** using stabilizer tableaux can handle up to\n", - " 100,000 qubits in polynomial time.\n", - "4. **Pauli algebra** provides the mathematical foundation for expressing\n", - " Hamiltonians and computing expectation values.\n", - "\n", - "From this notebook (SDK reference):\n", - "\n", - "5. **Pulse-level programming** goes below the gate abstraction to\n", - " control the actual microwave/laser signals. Virtual Z-rotations are\n", - " free; physical pulses have finite duration and error.\n", - "\n", - "6. **Operator representations** (Kraus, SuperOp, Choi, PTM) provide\n", - " different views of the same quantum channel, each useful for different\n", - " tasks: simulation, composition, physicality checks, and error analysis.\n", - "\n", - "7. **QASM/Quil interop** enables circuit exchange across the quantum\n", - " ecosystem. Round-trip parsing preserves circuit semantics.\n", - "\n", - "8. **Backends** abstract over execution targets (local simulators, cloud\n", - " hardware). The same code runs on any backend through the unified\n", - " `Submit/Status/Result` interface.\n", - "\n", - "9. **Observability hooks** provide production-grade visibility into\n", - " quantum workloads without coupling to any specific metrics or tracing\n", - " framework.\n", - "\n", - "---\n", - "\n", - "**Congratulations on completing the quantum computing curriculum!**\n", - "You now have the tools and understanding to build, simulate, transpile,\n", - "and run quantum algorithms -- from single-qubit gates all the way to\n", - "production-grade Hamiltonian simulation with observability.\n", - "\n", - "The quantum advantage is not in any single algorithm, but in the ability\n", - "to compose all of these pieces: express a problem as a Hamiltonian,\n", - "simulate it with the right decomposition, transpile to hardware\n", - "constraints, mitigate noise, and monitor the entire pipeline." - ] - } - ], - "metadata": { - "kernel_info": { - "name": "gonb" - }, - "kernelspec": { - "display_name": "Go (gonb)", - "language": "go", - "name": "gonb" - }, - "language_info": { - "codemirror_mode": "", - "file_extension": ".go", - "mimetype": "text/x-go", - "name": "go", - "nbconvert_exporter": "", - "pygments_lexer": "", - "version": "go1.26.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/README.md b/notebooks/README.md deleted file mode 100644 index da02f6b..0000000 --- a/notebooks/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Goqu Notebooks - -[Goqu](https://github.com/splch/goqu) -[gonb](https://github.com/janpfeifer/gonb) - -## Setup - -```bash -pip install jupyter -go install github.com/janpfeifer/gonb@latest && gonb --install -go install golang.org/x/tools/cmd/goimports@latest -go install golang.org/x/tools/gopls@latest -``` - -## Running - -```bash -cd notebooks -jupyter notebook -``` - -## Notes - -- Variables shared across cells are declared at the package level using `var` blocks -- Imports are placed in their own declaration cells (no `%%` prefix) -- Executable cells start with `%%` diff --git a/notebooks/go.mod b/notebooks/go.mod deleted file mode 100644 index 6a29c94..0000000 --- a/notebooks/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/splch/goqu/notebooks - -go 1.24.4 - -require ( - github.com/janpfeifer/gonb v0.11.3 - github.com/splch/goqu v0.0.0 -) - -require ( - github.com/go-logr/logr v1.4.2 // indirect - github.com/gofrs/uuid v4.4.0+incompatible // indirect - github.com/pkg/errors v0.9.1 // indirect - golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect - k8s.io/klog/v2 v2.130.1 // indirect -) - -replace github.com/splch/goqu => ../ diff --git a/notebooks/go.sum b/notebooks/go.sum deleted file mode 100644 index 4109159..0000000 --- a/notebooks/go.sum +++ /dev/null @@ -1,20 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= -github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/janpfeifer/gonb v0.11.3 h1:aO9hpD2oEEvh7ZZ/oEXP0Z1bFGE2IetEbpYP5JPDg9g= -github.com/janpfeifer/gonb v0.11.3/go.mod h1:2A4CHF6ry+Ql47bjW4VFpinXiW23AELCOuBzB1BZgQY= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= diff --git a/notebooks/tools.go b/notebooks/tools.go deleted file mode 100644 index b20d5c7..0000000 --- a/notebooks/tools.go +++ /dev/null @@ -1,53 +0,0 @@ -//go:build tools - -package notebooks - -import ( - _ "github.com/janpfeifer/gonb/gonbui" - _ "github.com/splch/goqu/algorithm/ampest" - _ "github.com/splch/goqu/algorithm/ansatz" - _ "github.com/splch/goqu/algorithm/counting" - _ "github.com/splch/goqu/algorithm/gradient" - _ "github.com/splch/goqu/algorithm/grover" - _ "github.com/splch/goqu/algorithm/hhl" - _ "github.com/splch/goqu/algorithm/mitigation" - _ "github.com/splch/goqu/algorithm/optim" - _ "github.com/splch/goqu/algorithm/qaoa" - _ "github.com/splch/goqu/algorithm/qpe" - _ "github.com/splch/goqu/algorithm/shor" - _ "github.com/splch/goqu/algorithm/textbook" - _ "github.com/splch/goqu/algorithm/trotter" - _ "github.com/splch/goqu/algorithm/vqc" - _ "github.com/splch/goqu/algorithm/vqd" - _ "github.com/splch/goqu/algorithm/vqe" - _ "github.com/splch/goqu/backend" - _ "github.com/splch/goqu/backend/local" - _ "github.com/splch/goqu/backend/mock" - _ "github.com/splch/goqu/circuit/builder" - _ "github.com/splch/goqu/circuit/draw" - _ "github.com/splch/goqu/circuit/gate" - _ "github.com/splch/goqu/circuit/ir" - _ "github.com/splch/goqu/circuit/param" - _ "github.com/splch/goqu/observe" - _ "github.com/splch/goqu/pulse" - _ "github.com/splch/goqu/pulse/waveform" - _ "github.com/splch/goqu/qasm/emitter" - _ "github.com/splch/goqu/qasm/parser" - _ "github.com/splch/goqu/quil/emitter" - _ "github.com/splch/goqu/sim/clifford" - _ "github.com/splch/goqu/sim/densitymatrix" - _ "github.com/splch/goqu/sim/noise" - _ "github.com/splch/goqu/sim/operator" - _ "github.com/splch/goqu/sim/pauli" - _ "github.com/splch/goqu/sim/statevector" - _ "github.com/splch/goqu/sweep" - _ "github.com/splch/goqu/transpile" - _ "github.com/splch/goqu/transpile/analysis" - _ "github.com/splch/goqu/transpile/decompose" - _ "github.com/splch/goqu/transpile/pass" - _ "github.com/splch/goqu/transpile/pipeline" - _ "github.com/splch/goqu/transpile/routing" - _ "github.com/splch/goqu/transpile/target" - _ "github.com/splch/goqu/transpile/verify" - _ "github.com/splch/goqu/viz" -) diff --git a/textbook/app.js b/textbook/app.js new file mode 100644 index 0000000..11f8305 --- /dev/null +++ b/textbook/app.js @@ -0,0 +1,29 @@ +const go = new Go(); +WebAssembly.instantiateStreaming(fetch(document.currentScript?.dataset.wasm || "../main.wasm"), go.importObject) + .then(r => { go.run(r.instance); document.querySelectorAll(".sandbox[data-autorun]").forEach(s => run(s.querySelector("button"))); }); + +function run(btn) { + const box = btn.closest(".sandbox"); + const code = box.querySelector(".code").value; + const shots = parseInt(box.dataset.shots || "1024", 10); + const out = box.querySelector(".output"); + out.innerHTML = "

Running...

"; + setTimeout(() => { + try { + const r = JSON.parse(window.runQASM(code, shots)); + if (r.error) { out.innerHTML = '
' + esc(r.error) + "
"; return; } + out.innerHTML = (r.circuit || "") + (r.histogram || "") + (r.bloch || ""); + } catch (e) { out.innerHTML = '
' + esc(e.message) + "
"; } + }, 0); +} + +function bloch(btn) { + const box = btn.closest(".sandbox"); + const v = (id) => parseFloat(box.querySelector("#" + id)?.value || "0"); + const out = box.querySelector(".output"); + try { + out.innerHTML = window.renderBloch(v("aRe"), v("aIm"), v("bRe"), v("bIm")); + } catch (e) { out.innerHTML = '
' + esc(e.message) + "
"; } +} + +function esc(s) { return s.replace(/&/g, "&").replace(//g, ">"); } diff --git a/textbook/chapters/01-qubits.html b/textbook/chapters/01-qubits.html new file mode 100644 index 0000000..8608da8 --- /dev/null +++ b/textbook/chapters/01-qubits.html @@ -0,0 +1,73 @@ + + + + + + Chapter 1: The Qubit - Goqu Textbook + + + + + + + +

Chapter 1: The Qubit

+ +

Classical Bits

+

A classical bit is either 0 or 1. A qubit (quantum bit) can be in a superposition of both. We write the two basis states as |0⟩ and |1⟩.

+

A general single-qubit state is:

+

α|0⟩ + β|1⟩

+

where α and β are complex numbers satisfying |α|2 + |β|2 = 1.

+ +

Your First Qubit

+

The Hadamard gate (h) puts a qubit into equal superposition. Run this circuit to see the result:

+ +
+ + +
+
+ +

You should see roughly equal counts of 0 and 1. Each measurement randomly collapses the superposition.

+ +

The Bloch Sphere

+

Every single-qubit state maps to a point on the Bloch sphere. The north pole is |0⟩, the south pole is |1⟩, and the equator contains superposition states like |+⟩ and |-⟩.

+

Adjust the amplitudes below to explore:

+ +
+ +
+ + + +
+
+ +

Try setting α = 1, β = 0 (the |0⟩ state at the north pole), then α = 0, β = 1 (the |1⟩ state at the south pole).

+ +

Multiple Shots

+

Quantum mechanics is probabilistic. Running the same circuit many times reveals the underlying probability distribution. Try changing the number of shots:

+ +
+ + +
+
+ +

With only 100 shots, the counts fluctuate noticeably. With 10,000 shots they converge toward 50/50.

+ + + + diff --git a/textbook/chapters/02-gates.html b/textbook/chapters/02-gates.html new file mode 100644 index 0000000..97148f3 --- /dev/null +++ b/textbook/chapters/02-gates.html @@ -0,0 +1,108 @@ + + + + + + Chapter 2: Quantum Gates - Goqu Textbook + + + + + + + +

Chapter 2: Quantum Gates

+ +

Quantum gates are unitary operations that transform qubit states. They are the quantum analog of classical logic gates. Unlike classical gates, quantum gates are always reversible.

+ +

The Pauli Gates

+

The three Pauli gates rotate a qubit by π radians around the X, Y, or Z axis of the Bloch sphere.

+
    +
  • X gate (bit flip): |0⟩ → |1⟩ and |1⟩ → |0⟩
  • +
  • Y gate: |0⟩ → i|1⟩ and |1⟩ → -i|0⟩
  • +
  • Z gate (phase flip): |0⟩ → |0⟩ and |1⟩ → -|1⟩
  • +
+ +
+ + +
+
+ +

The X gate flips |0⟩ to |1⟩, so measurement always returns 1.

+ +

The Hadamard Gate

+

The Hadamard gate creates equal superposition from a basis state:

+

H|0⟩ = (|0⟩ + |1⟩) / √2     H|1⟩ = (|0⟩ - |1⟩) / √2

+

Applying H twice returns to the original state:

+ +
+ + +
+
+ +

HH = I (the identity). You should see 100% |0⟩.

+ +

Rotation Gates

+

Rotation gates turn a qubit by an arbitrary angle around a given axis. rx, ry, and rz rotate around X, Y, and Z respectively.

+ +
+ + +
+
+ +

An RY(π/3) rotation gives P(0) = cos2(π/6) = 0.75 and P(1) = sin2(π/6) = 0.25.

+ +

The S and T Gates

+

The S gate is a π/2 phase rotation (√Z). The T gate is a π/4 phase rotation. These are important for fault-tolerant quantum computing.

+ +
+ + +
+
+ +

The T gate adds a relative phase of eiπ/4 to the |1⟩ component. After the second H gate, this phase becomes visible as a measurement bias.

+ +

Circuit Diagrams

+

Every example above shows a circuit diagram. Time flows left to right. Each horizontal line is a qubit wire. Gate boxes sit on the wires they act on. Try building a longer circuit:

+ +
+ + +
+
+ + + + diff --git a/textbook/chapters/03-measurement.html b/textbook/chapters/03-measurement.html new file mode 100644 index 0000000..7c1292d --- /dev/null +++ b/textbook/chapters/03-measurement.html @@ -0,0 +1,87 @@ + + + + + + Chapter 3: Measurement - Goqu Textbook + + + + + + + +

Chapter 3: Measurement

+ +

The Born Rule

+

When we measure a qubit in state α|0⟩ + β|1⟩, we get outcome 0 with probability |α|2 and outcome 1 with probability |β|2. This is the Born rule.

+

After measurement, the qubit collapses to the measured state. The superposition is destroyed.

+ +

Observing Probabilities

+

A single measurement gives a single bit. To estimate the underlying probabilities, we repeat the experiment many times. The histogram below shows results from 1024 shots of a Hadamard gate:

+ +
+ + +
+
+ +

Unequal Probabilities

+

By using rotation gates, we can prepare states with unequal measurement probabilities. An RY(π/4) rotation gives roughly 85% |0⟩ and 15% |1⟩:

+ +
+ + +
+
+ +

Multi-Qubit Measurement

+

When measuring multiple qubits, each outcome is a bitstring. For two qubits, the possible outcomes are 00, 01, 10, and 11:

+ +
+ + +
+
+ +

Two independent Hadamard gates produce a uniform distribution over all four outcomes.

+ +

Measurement Destroys Information

+

A key principle: measurement is irreversible. Once you observe a qubit, you cannot recover the original superposition. This is fundamentally different from classical computation, where you can always copy a bit.

+

The no-cloning theorem states that it is impossible to create an exact copy of an unknown quantum state. This has profound implications for quantum cryptography and error correction.

+ +

Computational Basis

+

All measurements in our circuits use the computational basis (also called the Z basis): {|0⟩, |1⟩}. To measure in a different basis, apply a basis-change gate before measurement. For example, to measure in the X basis (|+⟩, |-⟩), apply H first:

+ +
+ + +
+
+ +

The state |+⟩ measured in the X basis always gives 0. Without the second H, it would give 50/50 in the Z basis.

+ + + + diff --git a/textbook/chapters/04-entanglement.html b/textbook/chapters/04-entanglement.html new file mode 100644 index 0000000..b98d890 --- /dev/null +++ b/textbook/chapters/04-entanglement.html @@ -0,0 +1,123 @@ + + + + + + Chapter 4: Entanglement - Goqu Textbook + + + + + + + +

Chapter 4: Entanglement

+ +

Multi-Qubit Gates

+

The CNOT (controlled-NOT) gate is a two-qubit gate. It flips the target qubit if and only if the control qubit is |1⟩. In circuit diagrams, the control is a dot and the target is a circled plus.

+ +
+ + +
+
+ +

The control qubit (q[0]) is |1⟩, so the target (q[1]) gets flipped: outcome is always 11.

+ +

Bell States

+

Apply H to the first qubit, then CNOT. This creates a Bell state - the simplest example of entanglement:

+ +
+ + +
+
+ +

The result is always 00 or 11, never 01 or 10. The two qubits are perfectly correlated even though each individual measurement is random. This is the Bell state (|00⟩ + |11⟩) / √2.

+ +

All Four Bell States

+

There are four maximally entangled two-qubit states. They differ by an X and/or Z gate before the CNOT:

+ +
+ + +
+
+ +

Try modifying the circuit to prepare the other Bell states: add z q[0]; before the H gate, or x q[1]; after the CNOT.

+ +

GHZ States

+

Entanglement scales to many qubits. A GHZ state entangles three or more qubits into (|000⟩ + |111⟩) / √2:

+ +
+ + +
+
+ +

All three qubits are correlated: either all 0 or all 1.

+ +

Quantum Teleportation

+

Entanglement enables quantum teleportation: transferring a qubit state from one location to another using a shared Bell pair and two classical bits of communication.

+ +
+ + +
+
+ +

The state prepared on q[0] is teleported to q[2]. The protocol requires both a quantum channel (the entangled pair) and a classical channel (the measurement results).

+ +

Why Entanglement Matters

+

Entanglement is uniquely quantum - it has no classical analog. It is the resource that powers:

+
    +
  • Quantum teleportation - transferring states without physical qubit movement
  • +
  • Superdense coding - sending 2 classical bits with 1 qubit
  • +
  • Quantum key distribution - provably secure communication
  • +
  • Quantum algorithms - exponential speedups (next chapter)
  • +
+ + + + diff --git a/textbook/chapters/05-algorithms.html b/textbook/chapters/05-algorithms.html new file mode 100644 index 0000000..364699e --- /dev/null +++ b/textbook/chapters/05-algorithms.html @@ -0,0 +1,158 @@ + + + + + + Chapter 5: Algorithms - Goqu Textbook + + + + + + + +

Chapter 5: Quantum Algorithms

+ +

With qubits, gates, measurement, and entanglement, we have everything needed to build quantum algorithms that outperform their classical counterparts.

+ +

Deutsch-Jozsa Algorithm

+

Given a black-box function f: {0,1}n → {0,1} that is promised to be either constant (same output for all inputs) or balanced (0 for half, 1 for half), determine which.

+

Classically, you need 2n-1 + 1 queries in the worst case. Quantum: just one query.

+ +

Constant Oracle

+

A constant function always returns the same value. The algorithm measures all 0s:

+ +
+ + +
+
+ +

Measuring 00 means the function is constant.

+ +

Balanced Oracle

+

A balanced function returns 0 for half the inputs and 1 for the other half. The algorithm measures a non-zero bitstring:

+ +
+ + +
+
+ +

Measuring 11 (or any non-zero string) means the function is balanced. One quantum query replaces exponentially many classical ones.

+ +

Grover's Search Algorithm

+

Grover's algorithm searches an unstructured database of N items in O(√N) steps, a quadratic speedup over classical search.

+

The algorithm has three steps:

+
    +
  1. Superposition: apply H to all qubits
  2. +
  3. Oracle: flip the phase of the target state
  4. +
  5. Diffusion: amplify the marked state's amplitude
  6. +
+

Steps 2-3 are repeated √N times.

+ +

Grover's: 2-Qubit Example

+

Search for the state |11⟩ among 4 possibilities. With 2 qubits, one Grover iteration suffices:

+ +
+ + +
+
+ +

The target state |11⟩ is found with certainty in a single iteration.

+ +

Grover's: Searching for |10⟩

+

Change the oracle to mark a different state. To mark |10⟩, flip the second qubit before and after CZ:

+ +
+ + +
+
+ +

Now |10⟩ is found with certainty. Try modifying the oracle to search for |01⟩ or |00⟩.

+ +

What's Next

+

You now have the foundations of quantum computing:

+
    +
  • Qubits - the quantum unit of information
  • +
  • Gates - unitary transformations
  • +
  • Measurement - extracting classical information
  • +
  • Entanglement - quantum correlations
  • +
  • Algorithms - quantum speedups
  • +
+

Explore further with the Goqu API reference. The SDK includes implementations of Shor's algorithm, VQE, QAOA, quantum phase estimation, and more.

+ + + + diff --git a/textbook/index.html b/textbook/index.html new file mode 100644 index 0000000..feb8e1a --- /dev/null +++ b/textbook/index.html @@ -0,0 +1,28 @@ + + + + + + Goqu Quantum Computing Textbook + + + +

Goqu Quantum Computing Textbook

+

An interactive introduction to quantum computing. Every example runs live in your browser, powered by the Goqu SDK compiled to WebAssembly. Edit any code block and click Run to experiment.

+ +

Chapters

+
    +
  1. The Qubit - classical vs. quantum bits, superposition, the Bloch sphere
  2. +
  3. Quantum Gates - H, X, Y, Z, rotations, circuit diagrams
  4. +
  5. Measurement - Born rule, probability, histograms
  6. +
  7. Entanglement - CNOT, Bell states, multi-qubit circuits
  8. +
  9. Algorithms - Deutsch-Jozsa, Grover's search
  10. +
+ +

Running Locally

+

Build the WebAssembly module and start a local server:

+
make textbook
+make textbook-serve
+

Then open http://localhost:8080.

+ + diff --git a/textbook/style.css b/textbook/style.css new file mode 100644 index 0000000..24084b5 --- /dev/null +++ b/textbook/style.css @@ -0,0 +1,28 @@ +:root { --bg: #fff; --fg: #1a1a1a; --muted: #666; --border: #ddd; --sand-bg: #f8f8f8; --btn: #0066cc; --btn-text: #fff; --err: #c00; } +@media (prefers-color-scheme: dark) { + :root { --bg: #1a1a1a; --fg: #e0e0e0; --muted: #999; --border: #444; --sand-bg: #222; --btn: #4a9eff; --btn-text: #111; --err: #f66; } +} +*, *::before, *::after { box-sizing: border-box; } +body { max-width: 48rem; margin: 0 auto; padding: 1rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; line-height: 1.6; color: var(--fg); background: var(--bg); } +h1 { font-size: 1.8rem; margin: 1.5rem 0 1rem; } +h2 { font-size: 1.3rem; margin: 1.5rem 0 0.5rem; } +p, ul, ol { margin: 0.5rem 0; } +a { color: var(--btn); } +code { font-family: "SF Mono", "Fira Code", "Consolas", monospace; font-size: 0.9em; background: var(--sand-bg); padding: 0.1em 0.3em; border-radius: 3px; } +nav { display: flex; justify-content: space-between; padding: 0.5rem 0; border-bottom: 1px solid var(--border); margin-bottom: 1rem; font-size: 0.9rem; } +nav:last-of-type { border-bottom: none; border-top: 1px solid var(--border); margin-top: 2rem; padding-top: 0.5rem; } + +.sandbox { border: 1px solid var(--border); border-radius: 6px; padding: 1rem; margin: 1rem 0; background: var(--sand-bg); } +.sandbox .code { width: 100%; min-height: 5rem; padding: 0.5rem; font-family: "SF Mono", "Fira Code", "Consolas", monospace; font-size: 0.85rem; border: 1px solid var(--border); border-radius: 4px; resize: vertical; background: var(--bg); color: var(--fg); tab-size: 2; } +.sandbox button { margin-top: 0.5rem; padding: 0.4rem 1.2rem; border: none; border-radius: 4px; background: var(--btn); color: var(--btn-text); font-size: 0.85rem; cursor: pointer; } +.sandbox button:hover { opacity: 0.85; } +.sandbox .output { margin-top: 0.75rem; overflow-x: auto; text-align: center; } +.sandbox .output svg { max-width: 100%; height: auto; } +.sandbox .error { text-align: left; color: var(--err); font-family: monospace; font-size: 0.85rem; white-space: pre-wrap; margin: 0; padding: 0.5rem; background: transparent; } +.sandbox label { display: inline-block; font-size: 0.85rem; margin-right: 0.75rem; } +.sandbox input[type="range"] { vertical-align: middle; } + +.chapter-list { list-style: none; padding: 0; } +.chapter-list li { margin: 0.5rem 0; } +.chapter-list a { text-decoration: none; font-size: 1.1rem; } +.chapter-list a:hover { text-decoration: underline; } diff --git a/textbook/wasm/main.go b/textbook/wasm/main.go new file mode 100644 index 0000000..4100a1b --- /dev/null +++ b/textbook/wasm/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "encoding/json" + "syscall/js" + + "github.com/splch/goqu/circuit/draw" + "github.com/splch/goqu/qasm/parser" + "github.com/splch/goqu/sim/statevector" + "github.com/splch/goqu/viz" +) + +type result struct { + Circuit string `json:"circuit,omitempty"` + Histogram string `json:"histogram,omitempty"` + Bloch string `json:"bloch,omitempty"` + Error string `json:"error,omitempty"` +} + +func runQASM(_ js.Value, args []js.Value) any { + if len(args) < 2 { + return marshal(result{Error: "usage: runQASM(qasm, shots)"}) + } + qasm := args[0].String() + shots := args[1].Int() + if shots < 1 { + shots = 1024 + } + + circ, err := parser.ParseString(qasm) + if err != nil { + return marshal(result{Error: err.Error()}) + } + + r := result{Circuit: draw.SVG(circ)} + + nq := circ.NumQubits() + sim := statevector.New(nq) + defer sim.Close() + + counts, err := sim.Run(circ, shots) + if err != nil { + return marshal(result{Error: err.Error()}) + } + r.Histogram = viz.Histogram(counts) + + if nq == 1 { + sim2 := statevector.New(1) + defer sim2.Close() + if err := sim2.Evolve(circ); err == nil { + r.Bloch = viz.Bloch(sim2.StateVector()) + } + } + + return marshal(r) +} + +func renderBloch(_ js.Value, args []js.Value) any { + if len(args) < 4 { + return "" + } + state := []complex128{ + complex(args[0].Float(), args[1].Float()), + complex(args[2].Float(), args[3].Float()), + } + return viz.Bloch(state) +} + +func marshal(r result) string { + b, _ := json.Marshal(r) + return string(b) +} + +func main() { + js.Global().Set("runQASM", js.FuncOf(runQASM)) + js.Global().Set("renderBloch", js.FuncOf(renderBloch)) + select {} +} From 68027b7a253a665e7b11f3bdf57577eda3ea5d52 Mon Sep 17 00:00:00 2001 From: Spencer Churchill <25377399+splch@users.noreply.github.com> Date: Tue, 17 Mar 2026 22:18:42 -0700 Subject: [PATCH 2/5] replace benchmark workflow with textbook GitHub Pages deployment Deploy the interactive textbook to gh-pages branch root via peaceiris/actions-gh-pages. Remove the benchmark workflow and update README to link to the live textbook site. --- .github/workflows/benchmark.yml | 46 --------------------------------- .github/workflows/textbook.yml | 33 +++++++++++++++++++++++ README.md | 3 +-- 3 files changed, 34 insertions(+), 48 deletions(-) delete mode 100644 .github/workflows/benchmark.yml create mode 100644 .github/workflows/textbook.yml diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml deleted file mode 100644 index c290b9a..0000000 --- a/.github/workflows/benchmark.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Benchmark - -on: - push: - branches: [main] - -permissions: - contents: write - deployments: write - -jobs: - benchmark: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: "1.24" - - - name: Run benchmarks - run: | - go test ./sim/statevector/ -bench=. -count=5 -benchmem -run=^$ -timeout=10m | tee bench.txt - go test ./sim/densitymatrix/ -bench=. -count=5 -benchmem -run=^$ -timeout=10m | tee -a bench.txt - - - name: Create gh-pages branch if needed - run: | - if ! git ls-remote --exit-code origin gh-pages > /dev/null 2>&1; then - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git switch --orphan gh-pages - git commit --allow-empty -m "Initialize gh-pages for benchmark data" - git push origin gh-pages - git checkout main - fi - - - name: Store benchmark results - uses: benchmark-action/github-action-benchmark@v1 - with: - tool: go - output-file-path: bench.txt - github-token: ${{ secrets.GITHUB_TOKEN }} - auto-push: true - alert-threshold: "150%" - comment-on-alert: true - fail-on-alert: false - benchmark-data-dir-path: dev/bench diff --git a/.github/workflows/textbook.yml b/.github/workflows/textbook.yml new file mode 100644 index 0000000..da87f36 --- /dev/null +++ b/.github/workflows/textbook.yml @@ -0,0 +1,33 @@ +name: Textbook + +on: + push: + branches: [main] + paths: + - "textbook/**" + workflow_dispatch: + +permissions: + contents: write + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: "1.24" + + - name: Build WASM + run: | + cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" textbook/wasm_exec.js + cd textbook/wasm && GOOS=js GOARCH=wasm go build -ldflags="-w -s" -o ../main.wasm . + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./textbook + exclude_assets: ".github,wasm" diff --git a/README.md b/README.md index dbe4e30..e92e3f8 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,7 @@ q1: ─────X── ## Documentation - [API Reference](https://pkg.go.dev/github.com/splch/goqu) -- [Textbook](textbook/) -- [Benchmarks](https://splch.github.io/goqu/dev/bench/) +- [Textbook](https://splch.github.io/goqu/) ## Contributing From c54e57b0db25898cade7955422ba7285f6abd9da Mon Sep 17 00:00:00 2001 From: Spencer Churchill <25377399+splch@users.noreply.github.com> Date: Tue, 17 Mar 2026 22:22:37 -0700 Subject: [PATCH 3/5] exclude textbook/wasm from non-WASM CI checks Add //go:build js && wasm constraint so go vet and golangci-lint skip the WASM-only package on linux/amd64. Add -exclude-dir=textbook/wasm to gosec to avoid its SSA analyzer panic on syscall/js code. --- .github/workflows/ci.yml | 2 +- textbook/wasm/main.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ffd07f..0aacda4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,4 +105,4 @@ jobs: - name: Gosec uses: securego/gosec@master with: - args: -exclude=G115,G404 -exclude-dir=backend/braket -exclude-dir=observe/otelbridge -exclude-dir=observe/prombridge ./... + args: -exclude=G115,G404 -exclude-dir=backend/braket -exclude-dir=observe/otelbridge -exclude-dir=observe/prombridge -exclude-dir=textbook/wasm ./... diff --git a/textbook/wasm/main.go b/textbook/wasm/main.go index 4100a1b..533f64e 100644 --- a/textbook/wasm/main.go +++ b/textbook/wasm/main.go @@ -1,3 +1,5 @@ +//go:build js && wasm + package main import ( From d6e988f873aaf34e7f9f99769c648af844860cc7 Mon Sep 17 00:00:00 2001 From: Spencer Churchill <25377399+splch@users.noreply.github.com> Date: Tue, 17 Mar 2026 22:27:38 -0700 Subject: [PATCH 4/5] rewrite textbook with single qubit chapter and clean slate styles --- .../{01-qubits.html => 01-the-qubit.html} | 0 textbook/chapters/02-gates.html | 108 ------------ textbook/chapters/03-measurement.html | 87 ---------- textbook/chapters/04-entanglement.html | 123 -------------- textbook/chapters/05-algorithms.html | 158 ------------------ textbook/index.html | 13 +- textbook/style.css | 28 ---- 7 files changed, 2 insertions(+), 515 deletions(-) rename textbook/chapters/{01-qubits.html => 01-the-qubit.html} (100%) delete mode 100644 textbook/chapters/02-gates.html delete mode 100644 textbook/chapters/03-measurement.html delete mode 100644 textbook/chapters/04-entanglement.html delete mode 100644 textbook/chapters/05-algorithms.html diff --git a/textbook/chapters/01-qubits.html b/textbook/chapters/01-the-qubit.html similarity index 100% rename from textbook/chapters/01-qubits.html rename to textbook/chapters/01-the-qubit.html diff --git a/textbook/chapters/02-gates.html b/textbook/chapters/02-gates.html deleted file mode 100644 index 97148f3..0000000 --- a/textbook/chapters/02-gates.html +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - Chapter 2: Quantum Gates - Goqu Textbook - - - - - - - -

Chapter 2: Quantum Gates

- -

Quantum gates are unitary operations that transform qubit states. They are the quantum analog of classical logic gates. Unlike classical gates, quantum gates are always reversible.

- -

The Pauli Gates

-

The three Pauli gates rotate a qubit by π radians around the X, Y, or Z axis of the Bloch sphere.

-
    -
  • X gate (bit flip): |0⟩ → |1⟩ and |1⟩ → |0⟩
  • -
  • Y gate: |0⟩ → i|1⟩ and |1⟩ → -i|0⟩
  • -
  • Z gate (phase flip): |0⟩ → |0⟩ and |1⟩ → -|1⟩
  • -
- -
- - -
-
- -

The X gate flips |0⟩ to |1⟩, so measurement always returns 1.

- -

The Hadamard Gate

-

The Hadamard gate creates equal superposition from a basis state:

-

H|0⟩ = (|0⟩ + |1⟩) / √2     H|1⟩ = (|0⟩ - |1⟩) / √2

-

Applying H twice returns to the original state:

- -
- - -
-
- -

HH = I (the identity). You should see 100% |0⟩.

- -

Rotation Gates

-

Rotation gates turn a qubit by an arbitrary angle around a given axis. rx, ry, and rz rotate around X, Y, and Z respectively.

- -
- - -
-
- -

An RY(π/3) rotation gives P(0) = cos2(π/6) = 0.75 and P(1) = sin2(π/6) = 0.25.

- -

The S and T Gates

-

The S gate is a π/2 phase rotation (√Z). The T gate is a π/4 phase rotation. These are important for fault-tolerant quantum computing.

- -
- - -
-
- -

The T gate adds a relative phase of eiπ/4 to the |1⟩ component. After the second H gate, this phase becomes visible as a measurement bias.

- -

Circuit Diagrams

-

Every example above shows a circuit diagram. Time flows left to right. Each horizontal line is a qubit wire. Gate boxes sit on the wires they act on. Try building a longer circuit:

- -
- - -
-
- - - - diff --git a/textbook/chapters/03-measurement.html b/textbook/chapters/03-measurement.html deleted file mode 100644 index 7c1292d..0000000 --- a/textbook/chapters/03-measurement.html +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - Chapter 3: Measurement - Goqu Textbook - - - - - - - -

Chapter 3: Measurement

- -

The Born Rule

-

When we measure a qubit in state α|0⟩ + β|1⟩, we get outcome 0 with probability |α|2 and outcome 1 with probability |β|2. This is the Born rule.

-

After measurement, the qubit collapses to the measured state. The superposition is destroyed.

- -

Observing Probabilities

-

A single measurement gives a single bit. To estimate the underlying probabilities, we repeat the experiment many times. The histogram below shows results from 1024 shots of a Hadamard gate:

- -
- - -
-
- -

Unequal Probabilities

-

By using rotation gates, we can prepare states with unequal measurement probabilities. An RY(π/4) rotation gives roughly 85% |0⟩ and 15% |1⟩:

- -
- - -
-
- -

Multi-Qubit Measurement

-

When measuring multiple qubits, each outcome is a bitstring. For two qubits, the possible outcomes are 00, 01, 10, and 11:

- -
- - -
-
- -

Two independent Hadamard gates produce a uniform distribution over all four outcomes.

- -

Measurement Destroys Information

-

A key principle: measurement is irreversible. Once you observe a qubit, you cannot recover the original superposition. This is fundamentally different from classical computation, where you can always copy a bit.

-

The no-cloning theorem states that it is impossible to create an exact copy of an unknown quantum state. This has profound implications for quantum cryptography and error correction.

- -

Computational Basis

-

All measurements in our circuits use the computational basis (also called the Z basis): {|0⟩, |1⟩}. To measure in a different basis, apply a basis-change gate before measurement. For example, to measure in the X basis (|+⟩, |-⟩), apply H first:

- -
- - -
-
- -

The state |+⟩ measured in the X basis always gives 0. Without the second H, it would give 50/50 in the Z basis.

- - - - diff --git a/textbook/chapters/04-entanglement.html b/textbook/chapters/04-entanglement.html deleted file mode 100644 index b98d890..0000000 --- a/textbook/chapters/04-entanglement.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - Chapter 4: Entanglement - Goqu Textbook - - - - - - - -

Chapter 4: Entanglement

- -

Multi-Qubit Gates

-

The CNOT (controlled-NOT) gate is a two-qubit gate. It flips the target qubit if and only if the control qubit is |1⟩. In circuit diagrams, the control is a dot and the target is a circled plus.

- -
- - -
-
- -

The control qubit (q[0]) is |1⟩, so the target (q[1]) gets flipped: outcome is always 11.

- -

Bell States

-

Apply H to the first qubit, then CNOT. This creates a Bell state - the simplest example of entanglement:

- -
- - -
-
- -

The result is always 00 or 11, never 01 or 10. The two qubits are perfectly correlated even though each individual measurement is random. This is the Bell state (|00⟩ + |11⟩) / √2.

- -

All Four Bell States

-

There are four maximally entangled two-qubit states. They differ by an X and/or Z gate before the CNOT:

- -
- - -
-
- -

Try modifying the circuit to prepare the other Bell states: add z q[0]; before the H gate, or x q[1]; after the CNOT.

- -

GHZ States

-

Entanglement scales to many qubits. A GHZ state entangles three or more qubits into (|000⟩ + |111⟩) / √2:

- -
- - -
-
- -

All three qubits are correlated: either all 0 or all 1.

- -

Quantum Teleportation

-

Entanglement enables quantum teleportation: transferring a qubit state from one location to another using a shared Bell pair and two classical bits of communication.

- -
- - -
-
- -

The state prepared on q[0] is teleported to q[2]. The protocol requires both a quantum channel (the entangled pair) and a classical channel (the measurement results).

- -

Why Entanglement Matters

-

Entanglement is uniquely quantum - it has no classical analog. It is the resource that powers:

-
    -
  • Quantum teleportation - transferring states without physical qubit movement
  • -
  • Superdense coding - sending 2 classical bits with 1 qubit
  • -
  • Quantum key distribution - provably secure communication
  • -
  • Quantum algorithms - exponential speedups (next chapter)
  • -
- - - - diff --git a/textbook/chapters/05-algorithms.html b/textbook/chapters/05-algorithms.html deleted file mode 100644 index 364699e..0000000 --- a/textbook/chapters/05-algorithms.html +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - Chapter 5: Algorithms - Goqu Textbook - - - - - - - -

Chapter 5: Quantum Algorithms

- -

With qubits, gates, measurement, and entanglement, we have everything needed to build quantum algorithms that outperform their classical counterparts.

- -

Deutsch-Jozsa Algorithm

-

Given a black-box function f: {0,1}n → {0,1} that is promised to be either constant (same output for all inputs) or balanced (0 for half, 1 for half), determine which.

-

Classically, you need 2n-1 + 1 queries in the worst case. Quantum: just one query.

- -

Constant Oracle

-

A constant function always returns the same value. The algorithm measures all 0s:

- -
- - -
-
- -

Measuring 00 means the function is constant.

- -

Balanced Oracle

-

A balanced function returns 0 for half the inputs and 1 for the other half. The algorithm measures a non-zero bitstring:

- -
- - -
-
- -

Measuring 11 (or any non-zero string) means the function is balanced. One quantum query replaces exponentially many classical ones.

- -

Grover's Search Algorithm

-

Grover's algorithm searches an unstructured database of N items in O(√N) steps, a quadratic speedup over classical search.

-

The algorithm has three steps:

-
    -
  1. Superposition: apply H to all qubits
  2. -
  3. Oracle: flip the phase of the target state
  4. -
  5. Diffusion: amplify the marked state's amplitude
  6. -
-

Steps 2-3 are repeated √N times.

- -

Grover's: 2-Qubit Example

-

Search for the state |11⟩ among 4 possibilities. With 2 qubits, one Grover iteration suffices:

- -
- - -
-
- -

The target state |11⟩ is found with certainty in a single iteration.

- -

Grover's: Searching for |10⟩

-

Change the oracle to mark a different state. To mark |10⟩, flip the second qubit before and after CZ:

- -
- - -
-
- -

Now |10⟩ is found with certainty. Try modifying the oracle to search for |01⟩ or |00⟩.

- -

What's Next

-

You now have the foundations of quantum computing:

-
    -
  • Qubits - the quantum unit of information
  • -
  • Gates - unitary transformations
  • -
  • Measurement - extracting classical information
  • -
  • Entanglement - quantum correlations
  • -
  • Algorithms - quantum speedups
  • -
-

Explore further with the Goqu API reference. The SDK includes implementations of Shor's algorithm, VQE, QAOA, quantum phase estimation, and more.

- - - - diff --git a/textbook/index.html b/textbook/index.html index feb8e1a..ad6f25c 100644 --- a/textbook/index.html +++ b/textbook/index.html @@ -8,21 +8,12 @@

Goqu Quantum Computing Textbook

-

An interactive introduction to quantum computing. Every example runs live in your browser, powered by the Goqu SDK compiled to WebAssembly. Edit any code block and click Run to experiment.

Chapters

    -
  1. The Qubit - classical vs. quantum bits, superposition, the Bloch sphere
  2. -
  3. Quantum Gates - H, X, Y, Z, rotations, circuit diagrams
  4. -
  5. Measurement - Born rule, probability, histograms
  6. -
  7. Entanglement - CNOT, Bell states, multi-qubit circuits
  8. -
  9. Algorithms - Deutsch-Jozsa, Grover's search
  10. +
  11. The Qubit - ...
-

Running Locally

-

Build the WebAssembly module and start a local server:

-
make textbook
-make textbook-serve
-

Then open http://localhost:8080.

+

...

diff --git a/textbook/style.css b/textbook/style.css index 24084b5..e69de29 100644 --- a/textbook/style.css +++ b/textbook/style.css @@ -1,28 +0,0 @@ -:root { --bg: #fff; --fg: #1a1a1a; --muted: #666; --border: #ddd; --sand-bg: #f8f8f8; --btn: #0066cc; --btn-text: #fff; --err: #c00; } -@media (prefers-color-scheme: dark) { - :root { --bg: #1a1a1a; --fg: #e0e0e0; --muted: #999; --border: #444; --sand-bg: #222; --btn: #4a9eff; --btn-text: #111; --err: #f66; } -} -*, *::before, *::after { box-sizing: border-box; } -body { max-width: 48rem; margin: 0 auto; padding: 1rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; line-height: 1.6; color: var(--fg); background: var(--bg); } -h1 { font-size: 1.8rem; margin: 1.5rem 0 1rem; } -h2 { font-size: 1.3rem; margin: 1.5rem 0 0.5rem; } -p, ul, ol { margin: 0.5rem 0; } -a { color: var(--btn); } -code { font-family: "SF Mono", "Fira Code", "Consolas", monospace; font-size: 0.9em; background: var(--sand-bg); padding: 0.1em 0.3em; border-radius: 3px; } -nav { display: flex; justify-content: space-between; padding: 0.5rem 0; border-bottom: 1px solid var(--border); margin-bottom: 1rem; font-size: 0.9rem; } -nav:last-of-type { border-bottom: none; border-top: 1px solid var(--border); margin-top: 2rem; padding-top: 0.5rem; } - -.sandbox { border: 1px solid var(--border); border-radius: 6px; padding: 1rem; margin: 1rem 0; background: var(--sand-bg); } -.sandbox .code { width: 100%; min-height: 5rem; padding: 0.5rem; font-family: "SF Mono", "Fira Code", "Consolas", monospace; font-size: 0.85rem; border: 1px solid var(--border); border-radius: 4px; resize: vertical; background: var(--bg); color: var(--fg); tab-size: 2; } -.sandbox button { margin-top: 0.5rem; padding: 0.4rem 1.2rem; border: none; border-radius: 4px; background: var(--btn); color: var(--btn-text); font-size: 0.85rem; cursor: pointer; } -.sandbox button:hover { opacity: 0.85; } -.sandbox .output { margin-top: 0.75rem; overflow-x: auto; text-align: center; } -.sandbox .output svg { max-width: 100%; height: auto; } -.sandbox .error { text-align: left; color: var(--err); font-family: monospace; font-size: 0.85rem; white-space: pre-wrap; margin: 0; padding: 0.5rem; background: transparent; } -.sandbox label { display: inline-block; font-size: 0.85rem; margin-right: 0.75rem; } -.sandbox input[type="range"] { vertical-align: middle; } - -.chapter-list { list-style: none; padding: 0; } -.chapter-list li { margin: 0.5rem 0; } -.chapter-list a { text-decoration: none; font-size: 1.1rem; } -.chapter-list a:hover { text-decoration: underline; } From 39da16c5055f133cd662c17e55d583dc06d56c76 Mon Sep 17 00:00:00 2001 From: Spencer Churchill <25377399+splch@users.noreply.github.com> Date: Tue, 17 Mar 2026 22:30:07 -0700 Subject: [PATCH 5/5] stub out qubit chapter for blank slate --- textbook/chapters/01-the-qubit.html | 46 ----------------------------- 1 file changed, 46 deletions(-) diff --git a/textbook/chapters/01-the-qubit.html b/textbook/chapters/01-the-qubit.html index 8608da8..c3e0c6e 100644 --- a/textbook/chapters/01-the-qubit.html +++ b/textbook/chapters/01-the-qubit.html @@ -11,20 +11,10 @@

Chapter 1: The Qubit

-

Classical Bits

-

A classical bit is either 0 or 1. A qubit (quantum bit) can be in a superposition of both. We write the two basis states as |0⟩ and |1⟩.

-

A general single-qubit state is:

-

α|0⟩ + β|1⟩

-

where α and β are complex numbers satisfying |α|2 + |β|2 = 1.

- -

Your First Qubit

-

The Hadamard gate (h) puts a qubit into equal superposition. Run this circuit to see the result:

-
- -
-
- -

With only 100 shots, the counts fluctuate noticeably. With 10,000 shots they converge toward 50/50.

- -