QuantumLearning/notebooks/algorithms/module_01_deutsch_family/lab.ipynb

368 lines
18 KiB
Text

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Deutsch Family and Oracle Thinking Lab\n"
],
"id": "58468302"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<!-- COURSE_NAV_TOP -->\n",
"## Mainline Navigation\n",
"\n",
"Step 28 of 59. Follow the mainline in order and do not skip ahead.\n",
"\n",
"Previous notebook: [Deutsch Family and Oracle Thinking Lecture](lecture.ipynb)\n",
"\n",
"Next notebook: [Deutsch Family and Oracle Thinking Problems](problems.ipynb)\n",
"\n",
"Rule: finish this notebook top-to-bottom before you open the next one.\n"
],
"id": "c0e1efed"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The lab turns the oracle story into controlled edits. Each exercise asks you to preserve the question you are asking while changing one part of the mechanism. That discipline matters because algorithm notebooks become noisy very quickly when multiple causal features are changed at once.\n"
],
"id": "136a6bb3"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Lab Protocol\n",
"\n",
"\n",
" Before each edit, say what should remain invariant and what should change. Then render the circuit, inspect the diagram, inspect the preview counts, and decide whether the result matches the mechanism you think you are testing. If you cannot state the intended invariant, the edit is too loose.\n"
],
"id": "ac6bbeba"
},
{
"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": "50c7b992"
},
{
"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": "6d5918b3"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Lab 1: Deutsch Oracle Variants\n",
"\n",
"\n",
" Start with the smallest circuit and change only the oracle kind. You should be able to explain why the constant and balanced families differ before you run the code. Do not let the count histogram be the first time you decide what the circuit means.\n"
],
"id": "9722bf51"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"step_reference_table([{'marker': '[1]', 'code_focus': 'Prepare the query wire in superposition and the ancilla in the |-> state.', 'diagram_effect': 'The left side of the diagram separates the question register from the phase-sensitive ancilla.', 'why_it_matters': 'Without this preparation, the oracle behaves like an ordinary reversible gadget instead of a phase-encoding query.'}, {'marker': '[2]', 'code_focus': 'Apply an oracle that represents a promise class rather than a single numerical answer.', 'diagram_effect': 'The middle region becomes the semantic core of the circuit.', 'why_it_matters': 'Algorithmic design starts when you think in contracts and promise structures rather than in isolated gates.'}, {'marker': '[3]', 'code_focus': 'Use a final Hadamard on the query wire to convert hidden phase information into a measurable bit.', 'diagram_effect': 'The right side of the circuit shows a deliberate interference stage instead of direct readout after the oracle.', 'why_it_matters': 'Interference is the mechanism that cashes out the query advantage.'}, {'marker': '[4]', 'code_focus': 'Measure only the wire that carries the decision information.', 'diagram_effect': 'The reporting layer stays narrow and intentional.', 'why_it_matters': 'Professional design protects the evidence path and avoids measuring wires that are not part of the claim.'}])\n"
],
"id": "7e420990"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"editable_code = '\\nfrom qiskit import QuantumCircuit\\n\\ndef deutsch_oracle(kind: str) -> QuantumCircuit:\\n oracle = QuantumCircuit(2, name=f\"oracle_{kind}\")\\n if kind == \"balanced\":\\n oracle.cx(0, 1)\\n elif kind == \"constant_one\":\\n oracle.x(1)\\n elif kind != \"constant_zero\":\\n raise ValueError(\"kind must be constant_zero, constant_one, or balanced\")\\n return oracle\\n\\ncircuit = QuantumCircuit(2, 1)\\n# [1] Query wire in superposition, ancilla in |->\\ncircuit.h(0)\\ncircuit.x(1)\\ncircuit.h(1)\\n# [2] Oracle encodes the promise class\\ncircuit.compose(deutsch_oracle(\"balanced\"), inplace=True)\\n# [3] Final Hadamard turns phase into a measurable distinction\\ncircuit.h(0)\\n# [4] Only the decision wire needs to be reported\\ncircuit.measure(0, 0)\\n'\n",
"editable_circuit_lab(\n",
" initial_code=editable_code,\n",
" context={\"QuantumCircuit\": QuantumCircuit, \"simulate_counts\": simulate_counts},\n",
" title='Lab 1: Deutsch Oracle Variants',\n",
" instructions='Switch between constant_zero, constant_one, and balanced. Keep the explanation of the decision wire and the promise class explicit.',\n",
" shots=256,\n",
")\n"
],
"id": "79574c50"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def deutsch_counts(kind: str) -> dict[str, int]:\n",
" circuit = QuantumCircuit(2, 1)\n",
" circuit.h(0)\n",
" circuit.x(1)\n",
" circuit.h(1)\n",
" if kind == \"balanced\":\n",
" circuit.cx(0, 1)\n",
" elif kind == \"constant_one\":\n",
" circuit.x(1)\n",
" circuit.h(0)\n",
" circuit.measure(0, 0)\n",
" return simulate_counts(circuit, shots=256)\n",
"\n",
"results = {kind: deutsch_counts(kind) for kind in [\"constant_zero\", \"constant_one\", \"balanced\"]}\n",
"results\n"
],
"id": "13c4c361"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"quiz_block([{'prompt': 'If you change the Deutsch oracle from balanced to constant_zero, what should happen to the measured query bit?', 'options': ['It should reliably flip to 1', 'It should move toward 0 because the phase distinction disappears', 'It should become uniformly random'], 'correct_index': 1, 'explanation': 'A constant oracle does not create the same phase relation, so the final interference returns the constant verdict.'}, {'prompt': 'What is the right way to inspect a circuit edit in this lab?', 'options': ['Change several lines at once so the effect is obvious', 'Change one causal feature at a time and compare the diagram and counts together', 'Ignore the diagram and focus only on the text representation'], 'correct_index': 1, 'explanation': 'Controlled editing is the only way to attach causes to outcomes.'}], heading='Lab Checkpoint A')\n"
],
"id": "35dfe4b9"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"reflection_box('Which lab edit most improved your understanding of why the ancilla must be prepared as it is?')\n"
],
"id": "0ce114fa"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Lab 2: Deutsch-Jozsa Scaling\n",
"\n",
"\n",
" Move to a wider query register without abandoning the same mechanism. The point is to feel what scales and what does not. If your explanation changes from 'promise plus interference' to 'I copied a bigger circuit,' stop and reset.\n"
],
"id": "13d413c6"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"editable_code = '\\nfrom qiskit import QuantumCircuit\\n\\ndef deutsch_jozsa_oracle(kind: str = \"balanced\") -> QuantumCircuit:\\n oracle = QuantumCircuit(3, name=f\"dj_{kind}\")\\n if kind == \"balanced\":\\n oracle.cx(0, 2)\\n oracle.cx(1, 2)\\n elif kind == \"constant_one\":\\n oracle.x(2)\\n elif kind != \"constant_zero\":\\n raise ValueError(\"kind must be constant_zero, constant_one, or balanced\")\\n return oracle\\n\\ncircuit = QuantumCircuit(3, 2)\\n# Put both query wires into superposition.\\ncircuit.h([0, 1])\\n# Prepare the ancilla in |->\\ncircuit.x(2)\\ncircuit.h(2)\\n# Query the oracle contract.\\ncircuit.compose(deutsch_jozsa_oracle(\"balanced\"), inplace=True)\\n# Interference reveals whether the promise is constant or balanced.\\ncircuit.h([0, 1])\\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 2: Deutsch-Jozsa Promise Cases',\n",
" instructions='Toggle the oracle kind and explain how the all-zero result versus non-zero pattern tracks the promise class.',\n",
" shots=256,\n",
")\n"
],
"id": "c0f36eae"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def dj_counts(kind: str) -> dict[str, int]:\n",
" circuit = QuantumCircuit(3, 2)\n",
" circuit.h([0, 1])\n",
" circuit.x(2)\n",
" circuit.h(2)\n",
" if kind == \"balanced\":\n",
" circuit.cx(0, 2)\n",
" circuit.cx(1, 2)\n",
" elif kind == \"constant_one\":\n",
" circuit.x(2)\n",
" circuit.h([0, 1])\n",
" circuit.measure([0, 1], [0, 1])\n",
" return simulate_counts(circuit, shots=256)\n",
"\n",
"{kind: dj_counts(kind) for kind in [\"constant_zero\", \"constant_one\", \"balanced\"]}\n"
],
"id": "78c1fac4"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Lab 3: Kickback Ablation\n",
"\n",
"\n",
" The fastest way to discover whether you truly understand the mechanism is to break it on purpose. Remove or alter one preparation step and see whether you can predict exactly which explanatory sentence stops being true. This is a design habit, not just a debugging trick.\n"
],
"id": "6208f66e"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"editable_code = '\\nfrom qiskit import QuantumCircuit\\n\\ndef balanced_oracle() -> QuantumCircuit:\\n oracle = QuantumCircuit(2, name=\"balanced\")\\n oracle.cx(0, 1)\\n return oracle\\n\\ncircuit = QuantumCircuit(2, 1)\\n# Toggle these preparation choices to see phase kickback disappear.\\ncircuit.h(0)\\ncircuit.x(1)\\ncircuit.h(1)\\ncircuit.compose(balanced_oracle(), inplace=True)\\ncircuit.h(0)\\ncircuit.measure(0, 0)\\n'\n",
"editable_circuit_lab(\n",
" initial_code=editable_code,\n",
" context={\"QuantumCircuit\": QuantumCircuit, \"simulate_counts\": simulate_counts},\n",
" title='Lab 3: Phase-Kickback Ablation',\n",
" instructions='Delete or modify one preparation step at a time. Say which causal link you just removed and what verdict you expect to lose.',\n",
" shots=256,\n",
")\n"
],
"id": "f16e2aaf"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"intact = QuantumCircuit(2, 1)\n",
"intact.h(0)\n",
"intact.x(1)\n",
"intact.h(1)\n",
"intact.cx(0, 1)\n",
"intact.h(0)\n",
"intact.measure(0, 0)\n",
"\n",
"broken = QuantumCircuit(2, 1)\n",
"broken.h(0)\n",
"broken.x(1)\n",
"broken.cx(0, 1)\n",
"broken.h(0)\n",
"broken.measure(0, 0)\n",
"\n",
"{\"intact\": simulate_counts(intact, shots=256), \"broken\": simulate_counts(broken, shots=256)}\n"
],
"id": "20f8e747"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"quiz_block([{'prompt': 'What usually happens if you remove the ancilla Hadamard and keep the rest of the Deutsch circuit unchanged?', 'options': ['Phase kickback logic is broken, so the final decision bit loses its meaning', 'The query wire is automatically corrected by transpilation', 'Nothing changes because the ancilla is not measured'], 'correct_index': 0, 'explanation': 'The ancilla preparation is part of the mechanism, not optional decoration.'}, {'prompt': 'Why is Deutsch-Jozsa a useful engineering module and not only a historical algorithm?', 'options': ['It teaches how to scale promise-structured oracles and interference reasoning', 'It is the fastest route to hardware execution', 'It eliminates the need for later algorithm study'], 'correct_index': 0, 'explanation': 'The reusable lesson is how an oracle contract and interference stage compose.'}], heading='Lab Checkpoint B')\n"
],
"id": "85cf51d7"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Lab Debrief\n",
"\n",
"\n",
" The lab should leave you with a stricter sense of what counts as an explanation. A strong explanation in this module ties the promise class to the oracle body, the ancilla preparation to phase kickback, the final Hadamard to interference, and the measured wire to the actual decision being made. If any of those links felt fuzzy, that is useful information. The whole point of the bundle structure is to surface that fuzziness before later modules assume the pattern is already solid.\n"
],
"id": "02acf745"
},
{
"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": "3533ed49"
},
{
"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": "866d3ba3"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"reflection_box('Write a short review note about one Deutsch-Jozsa oracle candidate that looks plausible but does not clearly expose its promise class.')\n"
],
"id": "97a98104"
},
{
"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": "c708e751"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<!-- COURSE_NAV_BOTTOM -->\n",
"## What To Open Next\n",
"\n",
"Next notebook: [Deutsch Family and Oracle Thinking Problems](problems.ipynb)\n",
"\n",
"When you finish this notebook, open the next notebook shown above. Stay on the guarded mainline route.\n"
],
"id": "5d9c18ca"
}
],
"metadata": {
"kernelspec": {
"display_name": "QuantumLearning (.venv)",
"language": "python",
"name": "quantum-learning"
},
"language_info": {
"name": "python",
"version": "3.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}