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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- "
"
- ]
- },
- "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": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- ""
- ]
- },
- "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": [
- "