QuantumLearning/notebooks/algorithms/module_04_grover/lab.ipynb

352 lines
17 KiB
Text

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Grover and Amplitude Amplification Lab\n"
],
"id": "6aa48473"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The lab emphasizes target variation, iteration comparison, and oracle readability. The small search space is a feature, not a limitation, because it lets you see the mechanism before later reality bands complicate it.\n"
],
"id": "9c1cf229"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Lab Protocol\n",
"\n",
"\n",
" When you edit the target, inspect the oracle wrappers. When you edit the iteration count, predict whether you are improving or overshooting the amplification. When you edit the helper shape, ask whether the semantic role of each block stayed visible.\n"
],
"id": "9764cf5f"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pathlib import Path\n",
"import sys\n",
"\n",
"project_root = Path.cwd().resolve()\n",
"while not (project_root / \"pyproject.toml\").exists():\n",
" if project_root.parent == project_root:\n",
" raise RuntimeError(\"Could not locate the project root from this notebook.\")\n",
" project_root = project_root.parent\n",
"\n",
"src_path = project_root / \"src\"\n",
"if str(src_path) not in sys.path:\n",
" sys.path.insert(0, str(src_path))\n"
],
"id": "3e7046de"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from math import pi\n",
"\n",
"from quantum_learning import (\n",
" counts_to_probabilities,\n",
" draw_circuit,\n",
" editable_circuit_lab,\n",
" plot_counts,\n",
" plot_probabilities,\n",
" quiz_block,\n",
" reflection_box,\n",
" simulate_counts,\n",
" statevector_probabilities,\n",
" step_reference_table,\n",
")\n",
"from qiskit import QuantumCircuit\n",
"from qiskit.quantum_info import Statevector\n"
],
"id": "319d85e5"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Lab 1: Target Changes\n",
"\n",
"\n",
" Start by moving the marked state around the two-qubit search space. The question is not only whether the peak moves. The question is whether the target-marking logic remains readable enough that you can audit it.\n"
],
"id": "90c3751c"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"step_reference_table([{'marker': '[1]', 'code_focus': 'Prepare the search register in a uniform superposition.', 'diagram_effect': 'The circuit opens with a clean equal-weight state over all candidates.', 'why_it_matters': 'Amplitude amplification starts from a fair baseline, not from a pre-biased guess.'}, {'marker': '[2]', 'code_focus': 'Implement the oracle as a phase-marking operation on the chosen target.', 'diagram_effect': 'The middle block identifies the marked state without directly measuring it.', 'why_it_matters': 'Grover oracles mark by phase, not by classical tagging.'}, {'marker': '[3]', 'code_focus': 'Apply the diffuser as a reflection about the mean amplitude.', 'diagram_effect': 'The circuit mirrors the oracle block with a deliberate inversion pattern.', 'why_it_matters': 'The diffuser is the engineering heart of amplitude amplification.'}, {'marker': '[4]', 'code_focus': 'Choose the iteration count as a design decision, then measure.', 'diagram_effect': 'The right edge of the circuit closes the amplification loop with a concrete reporting contract.', 'why_it_matters': 'Over-iteration is a real design failure, not a harmless extra pass.'}])\n"
],
"id": "55e1a596"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"editable_code = '\\nfrom qiskit import QuantumCircuit\\n\\ndef grover_oracle(target: str = \"11\") -> QuantumCircuit:\\n oracle = QuantumCircuit(2, name=f\"oracle_{target}\")\\n if target[0] == \"0\":\\n oracle.x(1)\\n if target[1] == \"0\":\\n oracle.x(0)\\n oracle.cz(0, 1)\\n if target[1] == \"0\":\\n oracle.x(0)\\n if target[0] == \"0\":\\n oracle.x(1)\\n return oracle\\n\\ndef diffuser() -> QuantumCircuit:\\n circuit = QuantumCircuit(2, name=\"diffuser\")\\n circuit.h([0, 1])\\n circuit.x([0, 1])\\n circuit.cz(0, 1)\\n circuit.x([0, 1])\\n circuit.h([0, 1])\\n return circuit\\n\\ncircuit = QuantumCircuit(2, 2)\\n# [1] Uniform search state\\ncircuit.h([0, 1])\\n# [2] Mark the target by phase\\ncircuit.compose(grover_oracle(\"11\"), inplace=True)\\n# [3] Reflect about the mean\\ncircuit.compose(diffuser(), inplace=True)\\n# [4] Report the amplified candidate\\ncircuit.measure([0, 1], [0, 1])\\n'\n",
"editable_circuit_lab(\n",
" initial_code=editable_code,\n",
" context={\"QuantumCircuit\": QuantumCircuit, \"simulate_counts\": simulate_counts},\n",
" title='Lab 1: Target Changes',\n",
" instructions='Change the target string and verify that the X-wrapper logic marks the state you intended before you trust the counts.',\n",
" shots=256,\n",
")\n"
],
"id": "9f5c055f"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def run(target: str) -> dict[str, int]:\n",
" circuit = QuantumCircuit(2, 2)\n",
" circuit.h([0, 1])\n",
" if target[0] == \"0\":\n",
" circuit.x(1)\n",
" if target[1] == \"0\":\n",
" circuit.x(0)\n",
" circuit.cz(0, 1)\n",
" if target[1] == \"0\":\n",
" circuit.x(0)\n",
" if target[0] == \"0\":\n",
" circuit.x(1)\n",
" circuit.h([0, 1])\n",
" circuit.x([0, 1])\n",
" circuit.cz(0, 1)\n",
" circuit.x([0, 1])\n",
" circuit.h([0, 1])\n",
" circuit.measure([0, 1], [0, 1])\n",
" return simulate_counts(circuit, shots=256)\n",
"\n",
"{target: run(target) for target in [\"00\", \"01\", \"10\", \"11\"]}\n"
],
"id": "936fe7ed"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"quiz_block([{'prompt': 'If you change the Grover target from 11 to 01, what should stay invariant?', 'options': ['The overall oracle-plus-diffuser pattern', 'The exact same X wrappers around the oracle', 'The measured winning bitstring'], 'correct_index': 0, 'explanation': 'The motif is stable even though the marking details change.'}, {'prompt': 'What is the right way to inspect a target-change edit?', 'options': ['Check whether the oracle wrappers match the new target and whether the counts peak moved accordingly', 'Only inspect circuit depth', 'Only inspect the diffuser block'], 'correct_index': 0, 'explanation': 'The target lives in the oracle mapping and the resulting readout distribution.'}], heading='Lab Checkpoint A')\n"
],
"id": "67222d5d"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"reflection_box('What did the one-versus-two iteration comparison teach you about the geometric story of Grover?')\n"
],
"id": "f9d82ec6"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Lab 2: Iteration Comparison\n",
"\n",
"\n",
" Next compare one and two iterations explicitly. This is where the rotation language should stop feeling decorative and start feeling predictive.\n"
],
"id": "c4c81cca"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"editable_code = '\\nfrom qiskit import QuantumCircuit\\n\\ndef grover_oracle(target: str = \"10\") -> QuantumCircuit:\\n oracle = QuantumCircuit(2, name=f\"oracle_{target}\")\\n if target[0] == \"0\":\\n oracle.x(1)\\n if target[1] == \"0\":\\n oracle.x(0)\\n oracle.cz(0, 1)\\n if target[1] == \"0\":\\n oracle.x(0)\\n if target[0] == \"0\":\\n oracle.x(1)\\n return oracle\\n\\ndef diffuser() -> QuantumCircuit:\\n circuit = QuantumCircuit(2, name=\"diffuser\")\\n circuit.h([0, 1])\\n circuit.x([0, 1])\\n circuit.cz(0, 1)\\n circuit.x([0, 1])\\n circuit.h([0, 1])\\n return circuit\\n\\ndef grover_circuit(target: str = \"10\", iterations: int = 2) -> QuantumCircuit:\\n circuit = QuantumCircuit(2, 2)\\n circuit.h([0, 1])\\n for _ in range(iterations):\\n circuit.compose(grover_oracle(target), inplace=True)\\n circuit.compose(diffuser(), inplace=True)\\n circuit.measure([0, 1], [0, 1])\\n return circuit\\n\\ncircuit = grover_circuit(target=\"10\", iterations=2)\\n'\n",
"editable_circuit_lab(\n",
" initial_code=editable_code,\n",
" context={\"QuantumCircuit\": QuantumCircuit, \"simulate_counts\": simulate_counts},\n",
" title='Lab 2: Iteration Comparison',\n",
" instructions='Vary the iteration count and explain whether the target amplitude should still be moving toward or past the intended peak.',\n",
" shots=256,\n",
")\n"
],
"id": "fefa1a30"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def grover_counts(iterations: int) -> dict[str, int]:\n",
" circuit = QuantumCircuit(2, 2)\n",
" circuit.h([0, 1])\n",
" for _ in range(iterations):\n",
" circuit.cz(0, 1)\n",
" circuit.h([0, 1])\n",
" circuit.x([0, 1])\n",
" circuit.cz(0, 1)\n",
" circuit.x([0, 1])\n",
" circuit.h([0, 1])\n",
" circuit.measure([0, 1], [0, 1])\n",
" return simulate_counts(circuit, shots=256)\n",
"\n",
"{iterations: grover_counts(iterations) for iterations in [0, 1, 2]}\n"
],
"id": "31180520"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Lab 3: Oracle Readability\n",
"\n",
"\n",
" Finally, refactor the oracle helper itself. Keep the behavior fixed while changing how obvious the target-marking logic is. This is where the module becomes explicitly about maintainable design.\n"
],
"id": "445f9592"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"editable_code = '\\nfrom qiskit import QuantumCircuit\\n\\ndef grover_oracle(target: str = \"10\") -> QuantumCircuit:\\n oracle = QuantumCircuit(2, name=f\"oracle_{target}\")\\n if target[0] == \"0\":\\n oracle.x(1)\\n if target[1] == \"0\":\\n oracle.x(0)\\n oracle.cz(0, 1)\\n if target[1] == \"0\":\\n oracle.x(0)\\n if target[0] == \"0\":\\n oracle.x(1)\\n return oracle\\n\\ndef diffuser() -> QuantumCircuit:\\n circuit = QuantumCircuit(2, name=\"diffuser\")\\n circuit.h([0, 1])\\n circuit.x([0, 1])\\n circuit.cz(0, 1)\\n circuit.x([0, 1])\\n circuit.h([0, 1])\\n return circuit\\n\\ndef grover_circuit(target: str = \"10\", iterations: int = 2) -> QuantumCircuit:\\n circuit = QuantumCircuit(2, 2)\\n circuit.h([0, 1])\\n for _ in range(iterations):\\n circuit.compose(grover_oracle(target), inplace=True)\\n circuit.compose(diffuser(), inplace=True)\\n circuit.measure([0, 1], [0, 1])\\n return circuit\\n\\ncircuit = grover_circuit(target=\"10\", iterations=2)\\n'\n",
"editable_circuit_lab(\n",
" initial_code=editable_code,\n",
" context={\"QuantumCircuit\": QuantumCircuit, \"simulate_counts\": simulate_counts},\n",
" title='Lab 3: Oracle Readability',\n",
" instructions='Refactor the target-marking helper and decide whether the new version is easier or harder to review across target changes.',\n",
" shots=256,\n",
")\n"
],
"id": "57c53624"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"one_iteration = QuantumCircuit(2, 2)\n",
"one_iteration.h([0, 1])\n",
"one_iteration.cz(0, 1)\n",
"one_iteration.h([0, 1])\n",
"one_iteration.x([0, 1])\n",
"one_iteration.cz(0, 1)\n",
"one_iteration.x([0, 1])\n",
"one_iteration.h([0, 1])\n",
"one_iteration.measure([0, 1], [0, 1])\n",
"\n",
"two_iterations = QuantumCircuit(2, 2)\n",
"two_iterations.h([0, 1])\n",
"for _ in range(2):\n",
" two_iterations.cz(0, 1)\n",
" two_iterations.h([0, 1])\n",
" two_iterations.x([0, 1])\n",
" two_iterations.cz(0, 1)\n",
" two_iterations.x([0, 1])\n",
" two_iterations.h([0, 1])\n",
"two_iterations.measure([0, 1], [0, 1])\n",
"\n",
"{\n",
" \"one_iteration\": simulate_counts(one_iteration, shots=256),\n",
" \"two_iterations\": simulate_counts(two_iterations, shots=256),\n",
"}\n"
],
"id": "a245238a"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"quiz_block([{'prompt': 'What does two iterations on a 2-qubit Grover problem often illustrate?', 'options': ['Over-rotation or unnecessary extra work relative to the one-iteration optimum', 'Guaranteed improvement over one iteration', 'Automatic error mitigation'], 'correct_index': 0, 'explanation': 'A small search space makes the iteration-choice tradeoff easy to see.'}, {'prompt': 'Why compare separate oracle implementations for different targets?', 'options': ['To see whether the target-selection code stays readable and reviewable as the marking logic changes', 'Because only one target is physically valid', 'Because the diffuser depends on the target string'], 'correct_index': 0, 'explanation': 'Readable target handling matters for maintainable search code.'}], heading='Lab Checkpoint B')\n"
],
"id": "6d247da3"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Lab Debrief\n",
"\n",
"\n",
" The key lab outcome is that Grover should no longer feel like a single memorized diagram. It should feel like a modular routine with independent moving parts: target selection, diffusion, and iteration choice. Once those parts become independently discussable, the routine becomes something you can engineer rather than merely admire.\n"
],
"id": "be5189b4"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Why The Lab Is Slower Than A Tutorial\n",
"\n",
"These exercises are intentionally slower than ordinary click-through tutorials because the purpose is different. A tutorial can reward motion. A professional lab has to reward discrimination. You are being asked to notice which edit changed the semantic burden of the circuit, which edit only changed presentation, and which edit damaged the reporting contract even though the diagram still looked familiar. That is harder work, but it is the right work for someone trying to become a designer rather than a consumer of notebooks.\n"
],
"id": "e82e6c14"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Prediction Ledger\n",
"\n",
"If the lab begins to feel messy, return to the prediction ledger idea. Before each edit, write down what should remain invariant, what should move, and which evidence will decide the question. That tiny discipline is what keeps experiments from collapsing into aimless button pushing. It also mirrors how real engineering work scales. Good engineers do not only make changes. They keep track of what they expected the change to prove.\n"
],
"id": "2952143c"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"reflection_box('Write a short review note about one oracle helper that would be too opaque for serious use.')\n"
],
"id": "ff8dd199"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"reflection_box('Write one additional prediction habit you want to carry into later modules.')\n"
],
"id": "483aa598"
}
],
"metadata": {
"kernelspec": {
"display_name": "QuantumLearning (.venv)",
"language": "python",
"name": "quantum-learning"
},
"language_info": {
"name": "python",
"version": "3.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}