mirror of
https://github.com/saymrwulf/QuantumLearning.git
synced 2026-05-14 20:58:00 +00:00
2698 lines
138 KiB
Python
2698 lines
138 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
from textwrap import dedent
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
NOTEBOOKS = ROOT / "notebooks"
|
|
MODULE_01_DIR = NOTEBOOKS / "algorithms" / "module_01_deutsch_family"
|
|
MODULE_02_DIR = NOTEBOOKS / "algorithms" / "module_02_bernstein_vazirani"
|
|
MODULE_03_DIR = NOTEBOOKS / "algorithms" / "module_03_qft"
|
|
MODULE_04_DIR = NOTEBOOKS / "algorithms" / "module_04_grover"
|
|
|
|
|
|
def markdown_cell(text: str) -> dict:
|
|
cleaned = "\n".join(line.rstrip() for line in dedent(text).strip("\n").splitlines())
|
|
return {
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [line + "\n" for line in cleaned.splitlines()],
|
|
}
|
|
|
|
|
|
def code_cell(source: str) -> dict:
|
|
cleaned = dedent(source).strip("\n")
|
|
return {
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [line + "\n" for line in cleaned.splitlines()],
|
|
}
|
|
|
|
|
|
def _cell_id(index: int, cell: dict) -> str:
|
|
source = "".join(cell.get("source", []))
|
|
digest = hashlib.sha1(f"{cell.get('cell_type', 'cell')}:{index}:{source}".encode()).hexdigest()
|
|
return digest[:8]
|
|
|
|
|
|
def notebook(cells: list[dict]) -> dict:
|
|
normalized_cells = []
|
|
for index, cell in enumerate(cells):
|
|
payload = dict(cell)
|
|
payload.setdefault("id", _cell_id(index, payload))
|
|
normalized_cells.append(payload)
|
|
return {
|
|
"cells": normalized_cells,
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "QuantumLearning (.venv)",
|
|
"language": "python",
|
|
"name": "quantum-learning",
|
|
},
|
|
"language_info": {
|
|
"name": "python",
|
|
"version": "3.12",
|
|
},
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5,
|
|
}
|
|
|
|
|
|
def write_notebook(path: Path, payload: dict) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(json.dumps(payload, indent=2) + "\n")
|
|
|
|
|
|
def quiz_code(questions: list[dict], heading: str) -> dict:
|
|
return code_cell(f"quiz_block({questions!r}, heading={heading!r})")
|
|
|
|
|
|
def reflection_code(prompt: str) -> dict:
|
|
return code_cell(f"reflection_box({prompt!r})")
|
|
|
|
|
|
def compose_code(*blocks: str) -> str:
|
|
cleaned_blocks = []
|
|
for block in blocks:
|
|
cleaned = dedent(block).strip("\n")
|
|
if cleaned:
|
|
cleaned_blocks.append(cleaned)
|
|
return "\n\n".join(cleaned_blocks)
|
|
|
|
|
|
def editable_lab_code(
|
|
initial_code: str,
|
|
*,
|
|
title: str,
|
|
instructions: str,
|
|
context_source: str = "",
|
|
context_name: str = "simulate_counts",
|
|
shots: int = 256,
|
|
) -> dict:
|
|
return code_cell(
|
|
compose_code(
|
|
context_source,
|
|
f"""
|
|
editable_code = {initial_code!r}
|
|
editable_circuit_lab(
|
|
initial_code=editable_code,
|
|
context={{"QuantumCircuit": QuantumCircuit, "simulate_counts": {context_name}}},
|
|
title={title!r},
|
|
instructions={instructions!r},
|
|
shots={shots},
|
|
)
|
|
""",
|
|
)
|
|
)
|
|
|
|
|
|
SETUP = """
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
project_root = Path.cwd().resolve()
|
|
while not (project_root / "pyproject.toml").exists():
|
|
if project_root.parent == project_root:
|
|
raise RuntimeError("Could not locate the project root from this notebook.")
|
|
project_root = project_root.parent
|
|
|
|
src_path = project_root / "src"
|
|
if str(src_path) not in sys.path:
|
|
sys.path.insert(0, str(src_path))
|
|
"""
|
|
|
|
|
|
COMMON_IMPORTS = """
|
|
from math import pi
|
|
|
|
from quantum_learning import (
|
|
counts_to_probabilities,
|
|
draw_circuit,
|
|
editable_circuit_lab,
|
|
plot_counts,
|
|
plot_probabilities,
|
|
quiz_block,
|
|
reflection_box,
|
|
simulate_counts,
|
|
statevector_probabilities,
|
|
step_reference_table,
|
|
)
|
|
from qiskit import QuantumCircuit
|
|
from qiskit.quantum_info import Statevector
|
|
"""
|
|
|
|
|
|
DEUTSCH_STEP_REFS = [
|
|
{
|
|
"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.",
|
|
},
|
|
]
|
|
|
|
|
|
DEUTSCH_ANCHOR = """
|
|
from qiskit import QuantumCircuit
|
|
|
|
def deutsch_oracle(kind: str) -> QuantumCircuit:
|
|
oracle = QuantumCircuit(2, name=f"oracle_{kind}")
|
|
if kind == "balanced":
|
|
oracle.cx(0, 1)
|
|
elif kind == "constant_one":
|
|
oracle.x(1)
|
|
elif kind != "constant_zero":
|
|
raise ValueError("kind must be constant_zero, constant_one, or balanced")
|
|
return oracle
|
|
|
|
circuit = QuantumCircuit(2, 1)
|
|
# [1] Query wire in superposition, ancilla in |->
|
|
circuit.h(0)
|
|
circuit.x(1)
|
|
circuit.h(1)
|
|
# [2] Oracle encodes the promise class
|
|
circuit.compose(deutsch_oracle("balanced"), inplace=True)
|
|
# [3] Final Hadamard turns phase into a measurable distinction
|
|
circuit.h(0)
|
|
# [4] Only the decision wire needs to be reported
|
|
circuit.measure(0, 0)
|
|
"""
|
|
|
|
|
|
DEUTSCH_DJ_EDITABLE = """
|
|
from qiskit import QuantumCircuit
|
|
|
|
def deutsch_jozsa_oracle(kind: str = "balanced") -> QuantumCircuit:
|
|
oracle = QuantumCircuit(3, name=f"dj_{kind}")
|
|
if kind == "balanced":
|
|
oracle.cx(0, 2)
|
|
oracle.cx(1, 2)
|
|
elif kind == "constant_one":
|
|
oracle.x(2)
|
|
elif kind != "constant_zero":
|
|
raise ValueError("kind must be constant_zero, constant_one, or balanced")
|
|
return oracle
|
|
|
|
circuit = QuantumCircuit(3, 2)
|
|
# Put both query wires into superposition.
|
|
circuit.h([0, 1])
|
|
# Prepare the ancilla in |->
|
|
circuit.x(2)
|
|
circuit.h(2)
|
|
# Query the oracle contract.
|
|
circuit.compose(deutsch_jozsa_oracle("balanced"), inplace=True)
|
|
# Interference reveals whether the promise is constant or balanced.
|
|
circuit.h([0, 1])
|
|
circuit.measure([0, 1], [0, 1])
|
|
"""
|
|
|
|
|
|
DEUTSCH_KICKBACK_EDITABLE = """
|
|
from qiskit import QuantumCircuit
|
|
|
|
def balanced_oracle() -> QuantumCircuit:
|
|
oracle = QuantumCircuit(2, name="balanced")
|
|
oracle.cx(0, 1)
|
|
return oracle
|
|
|
|
circuit = QuantumCircuit(2, 1)
|
|
# Toggle these preparation choices to see phase kickback disappear.
|
|
circuit.h(0)
|
|
circuit.x(1)
|
|
circuit.h(1)
|
|
circuit.compose(balanced_oracle(), inplace=True)
|
|
circuit.h(0)
|
|
circuit.measure(0, 0)
|
|
"""
|
|
|
|
|
|
BV_STEP_REFS = [
|
|
{
|
|
"marker": "[1]",
|
|
"code_focus": "Prepare every query wire in superposition and the ancilla in |->.",
|
|
"diagram_effect": "The diagram begins with a wide preparation fan-out instead of a single branch.",
|
|
"why_it_matters": "Bernstein-Vazirani asks one structured question about several bits at once.",
|
|
},
|
|
{
|
|
"marker": "[2]",
|
|
"code_focus": "Route only the secret-controlled query wires into the ancilla.",
|
|
"diagram_effect": "The oracle body visually highlights which secret bits are active.",
|
|
"why_it_matters": "The hidden string is not mystical. It is literally the connectivity pattern inside the oracle.",
|
|
},
|
|
{
|
|
"marker": "[3]",
|
|
"code_focus": "Apply final Hadamards to decode the hidden pattern back onto the query register.",
|
|
"diagram_effect": "The second Hadamard layer closes the algorithmic sandwich.",
|
|
"why_it_matters": "This is the moment where phase-encoded structure becomes classical evidence.",
|
|
},
|
|
{
|
|
"marker": "[4]",
|
|
"code_focus": "Measure the query register in a bit order you can defend.",
|
|
"diagram_effect": "The readout layer makes the interface contract explicit.",
|
|
"why_it_matters": "Bit-order confusion is one of the easiest ways to sabotage an otherwise correct circuit.",
|
|
},
|
|
]
|
|
|
|
|
|
BV_ANCHOR = """
|
|
from qiskit import QuantumCircuit
|
|
|
|
def bv_oracle(secret: str = "101") -> QuantumCircuit:
|
|
oracle = QuantumCircuit(4, name=f"bv_{secret}")
|
|
ancilla = 3
|
|
for index, bit in enumerate(reversed(secret)):
|
|
if bit == "1":
|
|
oracle.cx(index, ancilla)
|
|
return oracle
|
|
|
|
circuit = QuantumCircuit(4, 3)
|
|
# [1] Prepare the query register and phase-sensitive ancilla.
|
|
circuit.h([0, 1, 2])
|
|
circuit.x(3)
|
|
circuit.h(3)
|
|
# [2] Query the structured oracle.
|
|
circuit.compose(bv_oracle("101"), inplace=True)
|
|
# [3] Decode the hidden pattern.
|
|
circuit.h([0, 1, 2])
|
|
# [4] Measure only the recovered string.
|
|
circuit.measure([0, 1, 2], [0, 1, 2])
|
|
"""
|
|
|
|
|
|
BV_ORDER_EDITABLE = """
|
|
from qiskit import QuantumCircuit
|
|
|
|
def bv_oracle(secret: str = "110") -> QuantumCircuit:
|
|
oracle = QuantumCircuit(4, name=f"bv_{secret}")
|
|
ancilla = 3
|
|
for index, bit in enumerate(reversed(secret)):
|
|
if bit == "1":
|
|
oracle.cx(index, ancilla)
|
|
return oracle
|
|
|
|
circuit = QuantumCircuit(4, 3)
|
|
circuit.h([0, 1, 2])
|
|
circuit.x(3)
|
|
circuit.h(3)
|
|
circuit.compose(bv_oracle("110"), inplace=True)
|
|
circuit.h([0, 1, 2])
|
|
# Change the measurement order only if you can explain the reporting contract.
|
|
circuit.measure([0, 1, 2], [0, 1, 2])
|
|
"""
|
|
|
|
|
|
BV_ALTERNATIVE_EDITABLE = """
|
|
from qiskit import QuantumCircuit
|
|
|
|
def bv_candidate(secret: str = "011", add_barriers: bool = False) -> QuantumCircuit:
|
|
circuit = QuantumCircuit(4, 3)
|
|
circuit.h([0, 1, 2])
|
|
circuit.x(3)
|
|
circuit.h(3)
|
|
if add_barriers:
|
|
circuit.barrier()
|
|
for index, bit in enumerate(reversed(secret)):
|
|
if bit == "1":
|
|
circuit.cx(index, 3)
|
|
if add_barriers:
|
|
circuit.barrier()
|
|
circuit.h([0, 1, 2])
|
|
circuit.measure([0, 1, 2], [0, 1, 2])
|
|
return circuit
|
|
|
|
circuit = bv_candidate(secret="011", add_barriers=True)
|
|
"""
|
|
|
|
|
|
QFT_STEP_REFS = [
|
|
{
|
|
"marker": "[1]",
|
|
"code_focus": "Start with a small, fully explicit QFT instead of hiding it behind a library call.",
|
|
"diagram_effect": "The circuit shows every controlled phase and swap directly.",
|
|
"why_it_matters": "A professional designer needs to know which pieces are essential and which are optional approximations.",
|
|
},
|
|
{
|
|
"marker": "[2]",
|
|
"code_focus": "Interpret each controlled phase as a graded correlation, not as a mysterious flourish.",
|
|
"diagram_effect": "The middle of the circuit becomes a ladder of phase relationships.",
|
|
"why_it_matters": "Controlled-phase structure is the real content of the QFT.",
|
|
},
|
|
{
|
|
"marker": "[3]",
|
|
"code_focus": "Use swaps to state the intended output ordering explicitly.",
|
|
"diagram_effect": "The end of the diagram makes the bit-reversal issue visible.",
|
|
"why_it_matters": "Output ordering is part of the interface, not a postscript.",
|
|
},
|
|
{
|
|
"marker": "[4]",
|
|
"code_focus": "Verify the transform with statevector or inverse-QFT checks instead of trusting the picture alone.",
|
|
"diagram_effect": "The notebook connects circuit graphics to basis-change evidence.",
|
|
"why_it_matters": "Basis-change circuits need stronger verification habits than simple compute-and-measure patterns.",
|
|
},
|
|
]
|
|
|
|
|
|
QFT_ANCHOR = """
|
|
from math import pi
|
|
from qiskit import QuantumCircuit
|
|
|
|
def qft3() -> QuantumCircuit:
|
|
circuit = QuantumCircuit(3, name="qft3")
|
|
# [1] Start with explicit local structure.
|
|
circuit.h(2)
|
|
circuit.cp(pi / 2, 1, 2)
|
|
circuit.cp(pi / 4, 0, 2)
|
|
# [2] Continue the controlled-phase ladder.
|
|
circuit.h(1)
|
|
circuit.cp(pi / 2, 0, 1)
|
|
circuit.h(0)
|
|
# [3] State the output ordering explicitly.
|
|
circuit.swap(0, 2)
|
|
return circuit
|
|
|
|
circuit = qft3()
|
|
"""
|
|
|
|
|
|
QFT_APPROX_EDITABLE = """
|
|
from math import pi
|
|
from qiskit import QuantumCircuit
|
|
|
|
def qft3(drop_smallest_angle: bool = False) -> QuantumCircuit:
|
|
circuit = QuantumCircuit(3, name="qft3")
|
|
circuit.h(2)
|
|
circuit.cp(pi / 2, 1, 2)
|
|
if not drop_smallest_angle:
|
|
circuit.cp(pi / 4, 0, 2)
|
|
circuit.h(1)
|
|
circuit.cp(pi / 2, 0, 1)
|
|
circuit.h(0)
|
|
circuit.swap(0, 2)
|
|
return circuit
|
|
|
|
circuit = qft3(drop_smallest_angle=True)
|
|
"""
|
|
|
|
|
|
QFT_RECOVERY_EDITABLE = """
|
|
from math import pi
|
|
from qiskit import QuantumCircuit
|
|
|
|
def qft3() -> QuantumCircuit:
|
|
circuit = QuantumCircuit(3, name="qft3")
|
|
circuit.h(2)
|
|
circuit.cp(pi / 2, 1, 2)
|
|
circuit.cp(pi / 4, 0, 2)
|
|
circuit.h(1)
|
|
circuit.cp(pi / 2, 0, 1)
|
|
circuit.h(0)
|
|
circuit.swap(0, 2)
|
|
return circuit
|
|
|
|
circuit = QuantumCircuit(3, 3)
|
|
circuit.x(0)
|
|
circuit.compose(qft3(), inplace=True)
|
|
circuit.compose(qft3().inverse(), inplace=True)
|
|
circuit.measure([0, 1, 2], [0, 1, 2])
|
|
"""
|
|
|
|
|
|
GROVER_STEP_REFS = [
|
|
{
|
|
"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.",
|
|
},
|
|
]
|
|
|
|
|
|
GROVER_ANCHOR = """
|
|
from qiskit import QuantumCircuit
|
|
|
|
def grover_oracle(target: str = "11") -> QuantumCircuit:
|
|
oracle = QuantumCircuit(2, name=f"oracle_{target}")
|
|
if target[0] == "0":
|
|
oracle.x(1)
|
|
if target[1] == "0":
|
|
oracle.x(0)
|
|
oracle.cz(0, 1)
|
|
if target[1] == "0":
|
|
oracle.x(0)
|
|
if target[0] == "0":
|
|
oracle.x(1)
|
|
return oracle
|
|
|
|
def diffuser() -> QuantumCircuit:
|
|
circuit = QuantumCircuit(2, name="diffuser")
|
|
circuit.h([0, 1])
|
|
circuit.x([0, 1])
|
|
circuit.cz(0, 1)
|
|
circuit.x([0, 1])
|
|
circuit.h([0, 1])
|
|
return circuit
|
|
|
|
circuit = QuantumCircuit(2, 2)
|
|
# [1] Uniform search state
|
|
circuit.h([0, 1])
|
|
# [2] Mark the target by phase
|
|
circuit.compose(grover_oracle("11"), inplace=True)
|
|
# [3] Reflect about the mean
|
|
circuit.compose(diffuser(), inplace=True)
|
|
# [4] Report the amplified candidate
|
|
circuit.measure([0, 1], [0, 1])
|
|
"""
|
|
|
|
|
|
GROVER_ITERATION_EDITABLE = """
|
|
from qiskit import QuantumCircuit
|
|
|
|
def grover_oracle(target: str = "10") -> QuantumCircuit:
|
|
oracle = QuantumCircuit(2, name=f"oracle_{target}")
|
|
if target[0] == "0":
|
|
oracle.x(1)
|
|
if target[1] == "0":
|
|
oracle.x(0)
|
|
oracle.cz(0, 1)
|
|
if target[1] == "0":
|
|
oracle.x(0)
|
|
if target[0] == "0":
|
|
oracle.x(1)
|
|
return oracle
|
|
|
|
def diffuser() -> QuantumCircuit:
|
|
circuit = QuantumCircuit(2, name="diffuser")
|
|
circuit.h([0, 1])
|
|
circuit.x([0, 1])
|
|
circuit.cz(0, 1)
|
|
circuit.x([0, 1])
|
|
circuit.h([0, 1])
|
|
return circuit
|
|
|
|
def grover_circuit(target: str = "10", iterations: int = 2) -> QuantumCircuit:
|
|
circuit = QuantumCircuit(2, 2)
|
|
circuit.h([0, 1])
|
|
for _ in range(iterations):
|
|
circuit.compose(grover_oracle(target), inplace=True)
|
|
circuit.compose(diffuser(), inplace=True)
|
|
circuit.measure([0, 1], [0, 1])
|
|
return circuit
|
|
|
|
circuit = grover_circuit(target="10", iterations=2)
|
|
"""
|
|
|
|
|
|
DEUTSCH_QUIZ_A = [
|
|
{
|
|
"prompt": "Why is the ancilla prepared in the |-> state in the Deutsch circuit?",
|
|
"options": [
|
|
"So the oracle's bit flip appears as a phase on the query branch",
|
|
"So the ancilla can be measured instead of the query wire",
|
|
"So the circuit uses fewer Hadamards",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The |-> preparation converts an oracle-controlled bit flip into phase kickback.",
|
|
},
|
|
{
|
|
"prompt": "What does the final Hadamard on the query wire accomplish?",
|
|
"options": [
|
|
"It compresses the circuit depth without changing meaning",
|
|
"It converts phase differences into a measurable basis distinction",
|
|
"It resets the ancilla before measurement",
|
|
],
|
|
"correct_index": 1,
|
|
"explanation": "Interference after the oracle is what reveals whether the promise is constant or balanced.",
|
|
},
|
|
{
|
|
"prompt": "Why is it useful to describe the oracle as a contract?",
|
|
"options": [
|
|
"Because the circuit depends on the exact gate library chosen by the backend",
|
|
"Because the algorithm reasons about a promise class, not an arbitrary implementation detail",
|
|
"Because all oracles must be measured to be trusted",
|
|
],
|
|
"correct_index": 1,
|
|
"explanation": "Oracle language keeps the emphasis on the function family and its promise, not on one ad hoc gate layout.",
|
|
},
|
|
]
|
|
|
|
|
|
DEUTSCH_QUIZ_B = [
|
|
{
|
|
"prompt": "What changes when you move from Deutsch to Deutsch-Jozsa?",
|
|
"options": [
|
|
"You stop using interference and switch to repeated measurement",
|
|
"You ask the same style of promise question on a larger query register",
|
|
"You no longer need an ancilla",
|
|
],
|
|
"correct_index": 1,
|
|
"explanation": "Deutsch-Jozsa scales the same interference logic to a promise on multiple inputs.",
|
|
},
|
|
{
|
|
"prompt": "What is the professional-design danger in copying an oracle from memory?",
|
|
"options": [
|
|
"The circuit may still run while no longer implementing the promised function family",
|
|
"The simulator will always reject the circuit",
|
|
"The compiler will remove the oracle automatically",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "A remembered diagram can be gate-correct yet semantically wrong if the oracle contract is not explicit.",
|
|
},
|
|
{
|
|
"prompt": "Why does the course keep asking what is being measured and why?",
|
|
"options": [
|
|
"Because measuring more wires always increases accuracy",
|
|
"Because the reporting contract should track the actual claim the circuit is making",
|
|
"Because Qiskit cannot draw unmeasured circuits",
|
|
],
|
|
"correct_index": 1,
|
|
"explanation": "Good circuit design protects the evidence path as carefully as the quantum body.",
|
|
},
|
|
]
|
|
|
|
|
|
DEUTSCH_LAB_QUIZ_A = [
|
|
{
|
|
"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.",
|
|
},
|
|
]
|
|
|
|
|
|
DEUTSCH_LAB_QUIZ_B = [
|
|
{
|
|
"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.",
|
|
},
|
|
]
|
|
|
|
|
|
DEUTSCH_PROBLEM_SETS = [
|
|
(
|
|
"Oracle Contract Check",
|
|
[
|
|
{
|
|
"prompt": "Which statement best describes a Deutsch oracle in this module?",
|
|
"options": [
|
|
"A reversible circuit implementing one member of a promised function family",
|
|
"Any two-qubit circuit with one CNOT",
|
|
"A measurement gadget that reveals the answer directly",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The oracle is defined by the function family and promise, not by a single gate mnemonic.",
|
|
},
|
|
{
|
|
"prompt": "Why is copying a balanced-oracle diagram into a constant case a design error?",
|
|
"options": [
|
|
"Because constants require more qubits",
|
|
"Because the circuit would no longer match the promise class being claimed",
|
|
"Because Qiskit forbids constant functions",
|
|
],
|
|
"correct_index": 1,
|
|
"explanation": "The semantic burden of the oracle changes with the promise class.",
|
|
},
|
|
],
|
|
),
|
|
(
|
|
"Interference Reasoning",
|
|
[
|
|
{
|
|
"prompt": "What information is actually being combined by the final Hadamard in Deutsch?",
|
|
"options": [
|
|
"The branch phases created by the oracle interaction",
|
|
"The counts collected during execution",
|
|
"The classical truth table of the function",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The last Hadamard converts branch-relative phase into basis amplitude.",
|
|
},
|
|
{
|
|
"prompt": "Which explanation is strongest when a learner says 'the circuit just magically knows'?",
|
|
"options": [
|
|
"It does not know; the oracle encodes structure and interference exposes it",
|
|
"Quantum circuits know because amplitudes are hidden variables",
|
|
"The simulator precomputes the answer and returns it",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Mechanistic language matters if you want professional control rather than awe.",
|
|
},
|
|
],
|
|
),
|
|
(
|
|
"Scaling To Deutsch-Jozsa",
|
|
[
|
|
{
|
|
"prompt": "What survives when the circuit scales from Deutsch to Deutsch-Jozsa?",
|
|
"options": [
|
|
"The oracle-contract idea, phase kickback, and final interference logic",
|
|
"Only the exact same two-qubit diagram",
|
|
"The need to measure the ancilla",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The pattern survives even though the specific wiring changes.",
|
|
},
|
|
{
|
|
"prompt": "What is the strongest reason to inspect all-zero versus non-zero outcomes in Deutsch-Jozsa?",
|
|
"options": [
|
|
"Those patterns are easier to draw",
|
|
"They encode the constant-versus-balanced promise distinction after interference",
|
|
"They avoid using classical registers",
|
|
],
|
|
"correct_index": 1,
|
|
"explanation": "The readout pattern is a claim about the promise class, not just a visual convenience.",
|
|
},
|
|
],
|
|
),
|
|
(
|
|
"Review Language",
|
|
[
|
|
{
|
|
"prompt": "Which review comment is strongest?",
|
|
"options": [
|
|
"This looks quantum enough",
|
|
"The oracle contract is unclear, so I cannot tell whether the circuit still distinguishes the promised cases",
|
|
"Please shorten the code by removing comments",
|
|
],
|
|
"correct_index": 1,
|
|
"explanation": "Professional review language targets the semantic burden of the circuit.",
|
|
},
|
|
{
|
|
"prompt": "What evidence should support a claim that a Deutsch circuit is correct?",
|
|
"options": [
|
|
"A clear oracle definition, a narrated interference mechanism, and counts that match the promise cases",
|
|
"Only a rendered diagram",
|
|
"Only a transpiler summary",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Correctness here is conceptual, structural, and empirical.",
|
|
},
|
|
],
|
|
),
|
|
]
|
|
|
|
|
|
DEUTSCH_STUDIO_QUIZ = [
|
|
{
|
|
"prompt": "What makes a good studio outcome in this module?",
|
|
"options": [
|
|
"A small family of oracle-based circuits with a defensible explanation of what changed and what stayed invariant",
|
|
"A single copied textbook circuit with no notes",
|
|
"The most gates you can fit onto two qubits",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Studio work is about deliberate variation and justification.",
|
|
},
|
|
{
|
|
"prompt": "Why is deliberate ablation useful in a design studio?",
|
|
"options": [
|
|
"It identifies which circuit regions actually carry the mechanism and which are accidental",
|
|
"It makes the code shorter by default",
|
|
"It avoids the need for measurements",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Ablation is a disciplined way to test causal understanding.",
|
|
},
|
|
]
|
|
|
|
|
|
BV_QUIZ_A = [
|
|
{
|
|
"prompt": "What is the hidden string in Bernstein-Vazirani, from a circuit-design point of view?",
|
|
"options": [
|
|
"A pattern of oracle-controlled interactions from query wires into the ancilla",
|
|
"A random measurement artifact",
|
|
"A post-processing choice made after simulation",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The secret is implemented structurally inside the oracle.",
|
|
},
|
|
{
|
|
"prompt": "Why is the final Hadamard layer still necessary in BV?",
|
|
"options": [
|
|
"It decodes phase-encoded structure back onto the query register",
|
|
"It reduces the number of classical bits needed",
|
|
"It prepares the ancilla for measurement",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The last Hadamards convert the hidden linear pattern into a classical report.",
|
|
},
|
|
{
|
|
"prompt": "Why is bit-order discipline a central engineering lesson in this module?",
|
|
"options": [
|
|
"Because a correct oracle can still be misreported by a sloppy measurement mapping",
|
|
"Because Qiskit requires alphabetical register names",
|
|
"Because secret strings must always be palindromes",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The circuit and the reporting interface have to agree on which bit is which.",
|
|
},
|
|
]
|
|
|
|
|
|
BV_QUIZ_B = [
|
|
{
|
|
"prompt": "What generalizable design idea does BV teach beyond the specific hidden-string problem?",
|
|
"options": [
|
|
"How to encode a structured linear rule in an oracle and recover it in one coherent pass",
|
|
"How to avoid ancillas in all algorithms",
|
|
"How to replace measurement with transpilation",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "BV is useful because it exposes a reusable structure, not just a party trick.",
|
|
},
|
|
{
|
|
"prompt": "Why is a barrier sometimes acceptable in this module's lab work?",
|
|
"options": [
|
|
"It can make the logical regions visible without changing the algorithmic claim",
|
|
"It improves the mathematics of the oracle",
|
|
"It changes the secret string automatically",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Barriers are presentation tools here, not algorithmic ingredients.",
|
|
},
|
|
{
|
|
"prompt": "What is the strongest argument for testing multiple secrets in the same notebook?",
|
|
"options": [
|
|
"It checks whether your builder really expresses a family rather than one lucky example",
|
|
"It forces the simulator to randomize less",
|
|
"It removes the need for comments",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Family-level reasoning is the real target of the module.",
|
|
},
|
|
]
|
|
|
|
|
|
BV_LAB_QUIZ_A = [
|
|
{
|
|
"prompt": "If the observed bitstring is reversed relative to the intended secret, what is the most likely cause?",
|
|
"options": [
|
|
"A reporting-contract mismatch between qubit order and classical readout order",
|
|
"The oracle has too many Hadamards",
|
|
"The ancilla should have been measured",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "This module trains you to treat bit order as interface design.",
|
|
},
|
|
{
|
|
"prompt": "What is the best way to debug a BV circuit that 'almost works'?",
|
|
"options": [
|
|
"Inspect which query wires actually control the ancilla and then inspect the measurement mapping",
|
|
"Add more entangling gates until the counts change",
|
|
"Ignore the secret and only inspect circuit depth",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The hidden string lives in the oracle connectivity and the reporting contract.",
|
|
},
|
|
]
|
|
|
|
|
|
BV_LAB_QUIZ_B = [
|
|
{
|
|
"prompt": "Why compare two BV candidates that produce the same output?",
|
|
"options": [
|
|
"To judge readability, interface clarity, and how well the secret structure is exposed in the code",
|
|
"Because one of them must be mathematically invalid",
|
|
"Because Qiskit requires multiple implementations",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Engineering quality includes more than behavioral equivalence.",
|
|
},
|
|
{
|
|
"prompt": "What would make a BV builder too clever?",
|
|
"options": [
|
|
"Hiding secret-bit routing behind opaque indexing so the oracle contract is no longer reviewable",
|
|
"Using a helper function at all",
|
|
"Keeping the ancilla unmeasured",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Abstraction is useful only while the causal burden stays visible.",
|
|
},
|
|
]
|
|
|
|
|
|
BV_PROBLEM_SETS = [
|
|
(
|
|
"Oracle Structure",
|
|
[
|
|
{
|
|
"prompt": "Which change directly changes the hidden string implemented by a BV oracle?",
|
|
"options": [
|
|
"Changing which query wires control the ancilla",
|
|
"Renaming the classical register",
|
|
"Adding a plot title",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The secret is the connectivity pattern into the ancilla.",
|
|
},
|
|
{
|
|
"prompt": "What is the strongest description of the BV oracle?",
|
|
"options": [
|
|
"A structured parity-like query on the input bits",
|
|
"A random entangling layer",
|
|
"A measurement-only gadget",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "BV is organized around a structured linear rule.",
|
|
},
|
|
],
|
|
),
|
|
(
|
|
"Bit Order And Reporting",
|
|
[
|
|
{
|
|
"prompt": "Which statement is most defensible about bit order?",
|
|
"options": [
|
|
"It can be ignored as long as counts look stable",
|
|
"It is part of the API between the circuit and the reader",
|
|
"It matters only in cloud execution",
|
|
],
|
|
"correct_index": 1,
|
|
"explanation": "If the learner cannot defend the mapping, the result is not well engineered.",
|
|
},
|
|
{
|
|
"prompt": "What kind of bug is a reversed-secret report?",
|
|
"options": [
|
|
"An interface bug that can mask a correct oracle",
|
|
"A hardware-only bug",
|
|
"A plotting bug with no circuit significance",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Interface bugs are still design bugs.",
|
|
},
|
|
],
|
|
),
|
|
(
|
|
"Reusable Motif",
|
|
[
|
|
{
|
|
"prompt": "What is the professional value of recognizing BV as a motif?",
|
|
"options": [
|
|
"You can reuse its structure when a task asks for hidden linear information in a circuit",
|
|
"You can stop learning other algorithms",
|
|
"You can avoid using classical post-processing forever",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The goal is transfer, not mere memorization.",
|
|
},
|
|
{
|
|
"prompt": "What survives when the secret string changes?",
|
|
"options": [
|
|
"The global circuit pattern and decoding logic",
|
|
"Only the exact same oracle wiring",
|
|
"The need to measure the ancilla",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The family structure is what matters.",
|
|
},
|
|
],
|
|
),
|
|
(
|
|
"Review Judgement",
|
|
[
|
|
{
|
|
"prompt": "Which review note is strongest?",
|
|
"options": [
|
|
"The code runs, so the oracle is fine",
|
|
"The secret-bit mapping is hidden behind index tricks, so the builder is hard to verify",
|
|
"Please use more barriers so it looks quantum",
|
|
],
|
|
"correct_index": 1,
|
|
"explanation": "Review should target what makes the circuit auditable or opaque.",
|
|
},
|
|
{
|
|
"prompt": "What evidence should accompany a claim that a BV notebook is correct?",
|
|
"options": [
|
|
"Several secrets, explicit oracle definitions, and counts matched against the intended reporting order",
|
|
"A single screenshot of one circuit",
|
|
"Only a statevector plot with no explanation",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Correctness here includes structural, interface, and empirical evidence.",
|
|
},
|
|
],
|
|
),
|
|
]
|
|
|
|
|
|
BV_STUDIO_QUIZ = [
|
|
{
|
|
"prompt": "What is the best studio target for this module?",
|
|
"options": [
|
|
"A small secret-recovery library where the oracle contract and reporting order are both reviewable",
|
|
"The shortest possible BV code cell",
|
|
"A notebook that uses only barriers and screenshots",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Studio work should promote reusable, auditable design rather than compression alone.",
|
|
},
|
|
{
|
|
"prompt": "Why is it useful to compare two secrets of different Hamming weights?",
|
|
"options": [
|
|
"It shows how the oracle body's density changes while the global motif stays stable",
|
|
"It changes the number of required classical bits",
|
|
"It removes the need for a final Hadamard layer",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Different secret densities stress the same design pattern in different ways.",
|
|
},
|
|
]
|
|
|
|
|
|
QFT_QUIZ_A = [
|
|
{
|
|
"prompt": "What is the most useful first description of the QFT in this course?",
|
|
"options": [
|
|
"A basis change that reorganizes phase information",
|
|
"A mysterious speedup box",
|
|
"A measurement replacement technique",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The QFT becomes legible when treated as an explicit basis transformation.",
|
|
},
|
|
{
|
|
"prompt": "Why are the controlled-phase gates the conceptual heart of the QFT?",
|
|
"options": [
|
|
"They progressively encode relative phase relationships across the wires",
|
|
"They are the only gates a simulator can draw",
|
|
"They replace swaps in all cases",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The ladder of controlled phases is where the transform actually lives.",
|
|
},
|
|
{
|
|
"prompt": "Why should swaps be explained rather than dismissed as cleanup?",
|
|
"options": [
|
|
"Because output ordering is part of the interface contract of the transform",
|
|
"Because swaps always improve fidelity",
|
|
"Because QFT cannot run without measuring every qubit",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Bit reversal is not housekeeping; it is a deliberate part of the output convention.",
|
|
},
|
|
]
|
|
|
|
|
|
QFT_QUIZ_B = [
|
|
{
|
|
"prompt": "What does an approximate QFT change first?",
|
|
"options": [
|
|
"It removes small-angle interactions to trade fidelity for structural simplicity",
|
|
"It replaces all Hadamards with X gates",
|
|
"It turns the transform into a classical FFT",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Approximation is a design tradeoff on controlled-phase detail.",
|
|
},
|
|
{
|
|
"prompt": "What is the best verification habit for a small QFT notebook?",
|
|
"options": [
|
|
"Use statevector evidence or inverse-QFT recovery checks, not just the circuit image",
|
|
"Trust the textbook diagram once it renders",
|
|
"Measure every qubit immediately after each gate",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Basis-change circuits need explicit evidence.",
|
|
},
|
|
{
|
|
"prompt": "Why does the QFT belong in an algorithmic-design band?",
|
|
"options": [
|
|
"Because it teaches a reusable basis-change motif that reappears inside larger algorithms",
|
|
"Because it is only useful as a historical example",
|
|
"Because it avoids all phase reasoning",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The module is about a transferable design pattern, not a museum piece.",
|
|
},
|
|
]
|
|
|
|
|
|
QFT_LAB_QUIZ_A = [
|
|
{
|
|
"prompt": "If you drop the final swap layer in a QFT notebook, what changes first?",
|
|
"options": [
|
|
"The output ordering convention becomes different even if the phase logic is otherwise intact",
|
|
"All controlled phases disappear",
|
|
"The circuit can no longer be simulated",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Removing swaps changes the interface contract of the transform.",
|
|
},
|
|
{
|
|
"prompt": "What is the best reason to edit one controlled-phase angle at a time?",
|
|
"options": [
|
|
"So you can connect a visible change in structure to a specific representational consequence",
|
|
"So the notebook runs faster",
|
|
"So the circuit has fewer qubits",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Controlled ablation is how you learn what each phase layer is doing.",
|
|
},
|
|
]
|
|
|
|
|
|
QFT_LAB_QUIZ_B = [
|
|
{
|
|
"prompt": "Why is QFT plus inverse-QFT a useful lab pattern?",
|
|
"options": [
|
|
"It checks whether your custom transform preserves information when composed with its inverse",
|
|
"It makes all swaps unnecessary",
|
|
"It amplifies noise automatically",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Recovery checks are a disciplined way to verify basis-change code.",
|
|
},
|
|
{
|
|
"prompt": "What does an approximate-QFT comparison teach best?",
|
|
"options": [
|
|
"How structural simplification can be defended as a tradeoff instead of guessed at",
|
|
"How to avoid ever drawing the circuit",
|
|
"How to replace controlled phases with measurements",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Approximation should be justified, not mystified.",
|
|
},
|
|
]
|
|
|
|
|
|
QFT_PROBLEM_SETS = [
|
|
(
|
|
"Basis Change",
|
|
[
|
|
{
|
|
"prompt": "Which phrase best captures the QFT at beginner-professional level?",
|
|
"options": [
|
|
"A basis change that reorganizes periodic structure into readable phase relationships",
|
|
"A black-box acceleration primitive with no circuit meaning",
|
|
"A measurement shortcut",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Mechanistic language matters if you want to design with the QFT later.",
|
|
},
|
|
{
|
|
"prompt": "Why is 'basis change' better language than 'magic transform'?",
|
|
"options": [
|
|
"Because it points you toward verification and interface questions",
|
|
"Because it removes the need for phase reasoning",
|
|
"Because it implies all amplitudes stay real",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Good language anchors good engineering questions.",
|
|
},
|
|
],
|
|
),
|
|
(
|
|
"Controlled-Phase Reading",
|
|
[
|
|
{
|
|
"prompt": "What does the smallest-angle controlled phase represent in a 3-qubit QFT?",
|
|
"options": [
|
|
"A weaker but still real contribution to the final phase pattern",
|
|
"A gate that can be ignored without consequence in every setting",
|
|
"A measurement of the least significant bit",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Its contribution is smaller, not conceptually meaningless.",
|
|
},
|
|
{
|
|
"prompt": "Why do QFT circuits deserve line-by-line explanation?",
|
|
"options": [
|
|
"Because many later algorithms embed them, so shallow memorization becomes expensive later",
|
|
"Because QFT never appears in serious work",
|
|
"Because all controlled phases are interchangeable",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "This module installs vocabulary for later reuse.",
|
|
},
|
|
],
|
|
),
|
|
(
|
|
"Ordering And Verification",
|
|
[
|
|
{
|
|
"prompt": "What is the strongest reason to discuss swaps explicitly?",
|
|
"options": [
|
|
"They encode a bit-order convention that affects how results are interpreted",
|
|
"They are visually attractive",
|
|
"They remove the need for inverse verification",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Ordering is part of the transform's external contract.",
|
|
},
|
|
{
|
|
"prompt": "Which evidence is most persuasive when validating a small QFT implementation?",
|
|
"options": [
|
|
"Statevector plots or inverse-composition checks tied to a written explanation",
|
|
"Only a final count histogram",
|
|
"Only gate count totals",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Basis-change circuits need representational evidence, not only sampled readout.",
|
|
},
|
|
],
|
|
),
|
|
(
|
|
"Approximation Tradeoffs",
|
|
[
|
|
{
|
|
"prompt": "Why might an engineer drop the smallest controlled-phase term?",
|
|
"options": [
|
|
"To trade some transform fidelity for structural simplification under a cost budget",
|
|
"To make the circuit classical",
|
|
"To eliminate all ordering issues",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Approximation belongs to explicit tradeoff reasoning.",
|
|
},
|
|
{
|
|
"prompt": "Which review comment is strongest?",
|
|
"options": [
|
|
"This approximation is acceptable because the omitted phase term is least significant and the verification notebook shows the recovery gap clearly",
|
|
"I removed some gates because the circuit looked busy",
|
|
"Approximate QFT means exact output with fewer gates",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "A professional defense ties structural changes to evidence and scope.",
|
|
},
|
|
],
|
|
),
|
|
]
|
|
|
|
|
|
QFT_STUDIO_QUIZ = [
|
|
{
|
|
"prompt": "What is the right studio objective for a QFT module?",
|
|
"options": [
|
|
"Build and defend a small transform variant with explicit choices about ordering, verification, and approximation",
|
|
"Memorize one diagram and redraw it from memory",
|
|
"Avoid discussing phases so the notebook stays simpler",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Studio work should convert the transform into a design object.",
|
|
},
|
|
{
|
|
"prompt": "Why compare full and approximate variants side by side?",
|
|
"options": [
|
|
"Because design quality depends on explicit cost-benefit reasoning, not only on correctness in the abstract",
|
|
"Because the exact transform is never useful",
|
|
"Because approximate transforms cannot be verified",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Side-by-side comparison is how tradeoffs become concrete.",
|
|
},
|
|
]
|
|
|
|
|
|
GROVER_QUIZ_A = [
|
|
{
|
|
"prompt": "What does a Grover oracle do in the small-search setting?",
|
|
"options": [
|
|
"It marks the target state by phase",
|
|
"It measures the winning answer directly",
|
|
"It replaces the diffuser",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The oracle marks, the diffuser amplifies.",
|
|
},
|
|
{
|
|
"prompt": "What is the diffuser best understood as?",
|
|
"options": [
|
|
"A reflection about the mean amplitude",
|
|
"A generic entangling block with no geometric meaning",
|
|
"A noise-mitigation trick",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Grover becomes teachable once the diffuser has geometric meaning.",
|
|
},
|
|
{
|
|
"prompt": "Why is iteration count part of the design story?",
|
|
"options": [
|
|
"Because too many iterations rotate past the target instead of improving forever",
|
|
"Because each iteration changes the number of qubits",
|
|
"Because the oracle stops working after one pass",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Amplitude amplification is a controlled rotation, not monotone accumulation.",
|
|
},
|
|
]
|
|
|
|
|
|
GROVER_QUIZ_B = [
|
|
{
|
|
"prompt": "What is the most important beginner-professional lesson in Grover?",
|
|
"options": [
|
|
"How oracle marking and diffusion compose into a tunable amplification routine",
|
|
"How to avoid thinking geometrically",
|
|
"How to replace search with measurement",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The module is about composing two reflections into a controlled search routine.",
|
|
},
|
|
{
|
|
"prompt": "Why should the oracle and diffuser often be implemented as separate helpers?",
|
|
"options": [
|
|
"Because they play different roles and must be inspected independently during review",
|
|
"Because Qiskit forbids inline composition",
|
|
"Because the diffuser is always backend-specific",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Separation improves auditability and experimentation.",
|
|
},
|
|
{
|
|
"prompt": "What would be a weak explanation of a good Grover result?",
|
|
"options": [
|
|
"The probabilities went up because quantum is strong",
|
|
"One iteration reflected the marked amplitude and then the mean-reflection boosted it",
|
|
"The oracle marked a state and the diffuser redistributed amplitudes around that mark",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The course is trying to eliminate hand-waving explanations like this.",
|
|
},
|
|
]
|
|
|
|
|
|
GROVER_LAB_QUIZ_A = [
|
|
{
|
|
"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.",
|
|
},
|
|
]
|
|
|
|
|
|
GROVER_LAB_QUIZ_B = [
|
|
{
|
|
"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.",
|
|
},
|
|
]
|
|
|
|
|
|
GROVER_PROBLEM_SETS = [
|
|
(
|
|
"Oracle And Diffuser Roles",
|
|
[
|
|
{
|
|
"prompt": "Which statement best distinguishes the oracle from the diffuser?",
|
|
"options": [
|
|
"The oracle marks a target by phase; the diffuser amplifies relative to the mean",
|
|
"The oracle and diffuser are interchangeable",
|
|
"The diffuser measures while the oracle transpiles",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The routine only makes sense if the two blocks have distinct semantic roles.",
|
|
},
|
|
{
|
|
"prompt": "Why is separating helpers useful in review?",
|
|
"options": [
|
|
"You can inspect whether the target-marking logic or the amplification logic is at fault",
|
|
"It guarantees lower depth",
|
|
"It removes the need for tests",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Separation sharpens diagnosis and discussion.",
|
|
},
|
|
],
|
|
),
|
|
(
|
|
"Geometric Reasoning",
|
|
[
|
|
{
|
|
"prompt": "What is the professional value of describing Grover as a rotation?",
|
|
"options": [
|
|
"It explains why more iterations are not automatically better",
|
|
"It means the circuit can only search circular data",
|
|
"It removes the need for oracle design",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The geometry explains the iteration tradeoff.",
|
|
},
|
|
{
|
|
"prompt": "Which phrase should you reject in this module?",
|
|
"options": [
|
|
"The routine just keeps getting stronger forever",
|
|
"The routine amplifies by composing two reflections",
|
|
"Iteration choice depends on search-space size",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "That phrase hides the actual mechanism.",
|
|
},
|
|
],
|
|
),
|
|
(
|
|
"Iteration Choice",
|
|
[
|
|
{
|
|
"prompt": "What is the strongest reason to compare one and two iterations in a 2-qubit search?",
|
|
"options": [
|
|
"It reveals the difference between useful amplification and over-rotation",
|
|
"It doubles the number of valid targets",
|
|
"It makes the oracle simpler",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Iteration choice is part of design, not just runtime length.",
|
|
},
|
|
{
|
|
"prompt": "What evidence best supports the claim that one iteration is better here?",
|
|
"options": [
|
|
"The count distribution peaks more strongly on the target under the intended search size",
|
|
"The circuit text looks shorter",
|
|
"The diffuser uses fewer comments",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Iteration claims should be supported by observed amplification behavior.",
|
|
},
|
|
],
|
|
),
|
|
(
|
|
"Review Language",
|
|
[
|
|
{
|
|
"prompt": "Which review comment is strongest?",
|
|
"options": [
|
|
"The target-marking logic is hard to audit because the X wrappers are hidden behind ambiguous indexing",
|
|
"This circuit looks aggressive",
|
|
"Please add more Hadamards because Grover uses many Hadamards",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Professional comments focus on auditability and semantic burden.",
|
|
},
|
|
{
|
|
"prompt": "What should accompany a final Grover recommendation?",
|
|
"options": [
|
|
"A defense of the oracle, diffuser, and iteration count tied to search size and observed outcomes",
|
|
"Only a single final histogram",
|
|
"Only a screenshot of the circuit drawing",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The recommendation should be structurally and empirically grounded.",
|
|
},
|
|
],
|
|
),
|
|
]
|
|
|
|
|
|
GROVER_STUDIO_QUIZ = [
|
|
{
|
|
"prompt": "What is a strong studio deliverable for Grover?",
|
|
"options": [
|
|
"A notebook comparing target choices and iteration counts with a written design recommendation",
|
|
"A single run of the textbook circuit",
|
|
"A list of gate names with no explanation",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "The studio should convert the routine into a defended design decision.",
|
|
},
|
|
{
|
|
"prompt": "Why is a tiny search space pedagogically useful here?",
|
|
"options": [
|
|
"Because the mechanism and the over-rotation risk are both easy to see locally",
|
|
"Because Grover works only on two qubits",
|
|
"Because the oracle no longer matters",
|
|
],
|
|
"correct_index": 0,
|
|
"explanation": "Small systems make the geometry visible.",
|
|
},
|
|
]
|
|
|
|
|
|
MODULES = [
|
|
{
|
|
"dir": MODULE_01_DIR,
|
|
"lecture_title": "Deutsch Family and Oracle Thinking",
|
|
"slug": "deutsch",
|
|
"lecture_intro": """
|
|
The first algorithmic-design module has a narrow but important purpose. It introduces the moment when a circuit stops being only a collection of gates and becomes a structured question. Deutsch and Deutsch-Jozsa are ideal for that transition because they are small enough to inspect line by line and deep enough to expose the full algorithmic pattern: prepare a meaningful superposition, define an oracle contract, let the oracle encode structure into phase, and then use interference to make the hidden distinction observable. That pattern will reappear in later modules in richer forms, so it needs to be learned as a design story rather than as a historical anecdote.
|
|
""",
|
|
"learning_objective": """
|
|
By the end of this lecture you should be able to explain why the oracle is described as a promise-bearing contract, why the ancilla preparation matters, how phase kickback and interference divide the work, and how the Deutsch and Deutsch-Jozsa circuits are related as members of one family rather than as unrelated textbook diagrams. You should also be able to critique an implementation that runs but no longer matches the intended promise class.
|
|
""",
|
|
"lecture_sections": [
|
|
"""
|
|
Query problems are the right place to begin algorithmic design because they force you to state what the circuit is actually being asked to decide. In many beginner notebooks the learner adds gates and only later asks what information the circuit is supposed to reveal. Query algorithms reverse that habit. They begin with a decision task and then ask what circuit structure could make that task cheaper, cleaner, or more revealing. Deutsch is intentionally tiny, but it already demands professional discipline: what are the allowed functions, what promise are we relying on, and what counts as valid evidence that the circuit distinguished the promised cases?
|
|
""",
|
|
"""
|
|
The word oracle can easily become a source of confusion. In this course, an oracle is not a mystical black box and not a free pass to skip engineering. It is a reversible implementation of a function family with a stated interface and promise. That definition matters because every later design decision depends on it. If you do not know whether the oracle represents a constant case or a balanced case, or if you cannot explain how the oracle is wired into the surrounding preparation and readout stages, then the rest of the notebook becomes decoration around an undefined center. Good algorithmic design starts by making the center explicit.
|
|
""",
|
|
"""
|
|
The ancilla preparation in the Deutsch family is the first place where professional explanation must replace beginner myth. Preparing the ancilla in |-> is not a ritual. It is a mechanism that turns a reversible bit-flip response into phase information on the query branches. That is the whole point of phase kickback in this context. Once the oracle has written its answer into branch-relative phase instead of into a directly measured output bit, the final Hadamard on the query register can convert that phase pattern into a basis distinction. If you cannot narrate those two conversions clearly, you do not yet control the circuit.
|
|
""",
|
|
"""
|
|
Deutsch-Jozsa is then best read as a scaling move, not as a new kind of magic. The query register becomes wider, the promise class becomes a global statement about many inputs, and the final measurement pattern becomes a structured verdict on that promise. But the algorithmic skeleton stays the same. This is the pedagogical payoff of the module. You are not meant to memorize one two-qubit circuit and then a larger one. You are meant to notice which design ideas survive when the surface grows: superposition as a question format, the oracle as a contract, and interference as the readout mechanism.
|
|
""",
|
|
"""
|
|
There is also a subtle engineering lesson hiding inside these small circuits. In query algorithms, measuring the wrong wire or writing an ambiguous oracle helper can ruin the notebook even when the diagram still looks plausible. That is why this lecture keeps talking about reporting contracts and reviewability. Once algorithms enter the course, the danger of plausible nonsense increases. A circuit can look recognizably textbook and still fail the semantic burden of the problem. Professional growth means getting stricter about what exactly a rendered circuit is claiming.
|
|
""",
|
|
"""
|
|
The result is that the Deutsch family is less about its eventual practical value and more about the habits it installs. It teaches that algorithms are composed arguments. The preparation stage states the question format. The oracle stage injects promised structure. The interference stage extracts the useful distinction. The measurement stage reports only the claim-bearing evidence. If you can read and edit the family at that level, later algorithm modules will feel continuous instead of disconnected.
|
|
""",
|
|
],
|
|
"anchor_intro": """
|
|
The anchor circuit below keeps the mechanism as bare as possible. Read the reference table first. Then inspect the editable code and make small changes. The goal is not to produce novelty yet. The goal is to make the mechanism legible enough that every line earns its place.
|
|
""",
|
|
"step_refs": DEUTSCH_STEP_REFS,
|
|
"anchor_code": DEUTSCH_ANCHOR,
|
|
"analysis_code": """
|
|
def deutsch_case(kind: str) -> dict[str, object]:
|
|
circuit = QuantumCircuit(2, 1)
|
|
circuit.h(0)
|
|
circuit.x(1)
|
|
circuit.h(1)
|
|
if kind == "balanced":
|
|
circuit.cx(0, 1)
|
|
elif kind == "constant_one":
|
|
circuit.x(1)
|
|
elif kind != "constant_zero":
|
|
raise ValueError("unexpected kind")
|
|
circuit.h(0)
|
|
circuit.measure(0, 0)
|
|
counts = simulate_counts(circuit, shots=256)
|
|
return {"kind": kind, "counts": counts}
|
|
|
|
[deutsch_case(kind) for kind in ["constant_zero", "constant_one", "balanced"]]
|
|
""",
|
|
"lecture_quiz_a": DEUTSCH_QUIZ_A,
|
|
"lecture_quiz_b": DEUTSCH_QUIZ_B,
|
|
"lecture_review_sections": [
|
|
"""
|
|
A good rule of thumb is that if you remove the words promise, oracle contract, or interference from your explanation and the notebook suddenly sounds vague, then the explanation was not yet strong enough. Those are not ornamental words in this module. They are the names of the burdens being carried by different regions of the circuit. The point of a lecture notebook is to make those burdens explicit before the lab asks you to disturb them.
|
|
""",
|
|
"""
|
|
Another useful habit is to ask what would count as a real refutation of your implementation. If a balanced oracle still reports the constant verdict, you have a meaningful failure. If your measurement mapping has drifted so the reported bit no longer corresponds to the decision wire, you also have a meaningful failure. This refutation mindset is what moves the module away from passive admiration and toward engineering control.
|
|
""",
|
|
],
|
|
"lecture_reflections": [
|
|
"Write a paragraph explaining phase kickback in the Deutsch circuit without using the word magic.",
|
|
"Describe one way an oracle helper could be syntactically neat but semantically misleading in this module.",
|
|
],
|
|
"lab_intro": """
|
|
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.
|
|
""",
|
|
"lab_protocol": """
|
|
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.
|
|
""",
|
|
"lab_sections": [
|
|
{
|
|
"heading": "Lab 1: Deutsch Oracle Variants",
|
|
"intro": """
|
|
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.
|
|
""",
|
|
"step_refs": DEUTSCH_STEP_REFS,
|
|
"editable_code": DEUTSCH_ANCHOR,
|
|
"editable_title": "Lab 1: Deutsch Oracle Variants",
|
|
"editable_instructions": "Switch between constant_zero, constant_one, and balanced. Keep the explanation of the decision wire and the promise class explicit.",
|
|
"after_code": """
|
|
def deutsch_counts(kind: str) -> dict[str, int]:
|
|
circuit = QuantumCircuit(2, 1)
|
|
circuit.h(0)
|
|
circuit.x(1)
|
|
circuit.h(1)
|
|
if kind == "balanced":
|
|
circuit.cx(0, 1)
|
|
elif kind == "constant_one":
|
|
circuit.x(1)
|
|
circuit.h(0)
|
|
circuit.measure(0, 0)
|
|
return simulate_counts(circuit, shots=256)
|
|
|
|
results = {kind: deutsch_counts(kind) for kind in ["constant_zero", "constant_one", "balanced"]}
|
|
results
|
|
""",
|
|
},
|
|
{
|
|
"heading": "Lab 2: Deutsch-Jozsa Scaling",
|
|
"intro": """
|
|
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.
|
|
""",
|
|
"step_refs": None,
|
|
"editable_code": DEUTSCH_DJ_EDITABLE,
|
|
"editable_title": "Lab 2: Deutsch-Jozsa Promise Cases",
|
|
"editable_instructions": "Toggle the oracle kind and explain how the all-zero result versus non-zero pattern tracks the promise class.",
|
|
"after_code": """
|
|
def dj_counts(kind: str) -> dict[str, int]:
|
|
circuit = QuantumCircuit(3, 2)
|
|
circuit.h([0, 1])
|
|
circuit.x(2)
|
|
circuit.h(2)
|
|
if kind == "balanced":
|
|
circuit.cx(0, 2)
|
|
circuit.cx(1, 2)
|
|
elif kind == "constant_one":
|
|
circuit.x(2)
|
|
circuit.h([0, 1])
|
|
circuit.measure([0, 1], [0, 1])
|
|
return simulate_counts(circuit, shots=256)
|
|
|
|
{kind: dj_counts(kind) for kind in ["constant_zero", "constant_one", "balanced"]}
|
|
""",
|
|
},
|
|
{
|
|
"heading": "Lab 3: Kickback Ablation",
|
|
"intro": """
|
|
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.
|
|
""",
|
|
"step_refs": None,
|
|
"editable_code": DEUTSCH_KICKBACK_EDITABLE,
|
|
"editable_title": "Lab 3: Phase-Kickback Ablation",
|
|
"editable_instructions": "Delete or modify one preparation step at a time. Say which causal link you just removed and what verdict you expect to lose.",
|
|
"after_code": """
|
|
intact = QuantumCircuit(2, 1)
|
|
intact.h(0)
|
|
intact.x(1)
|
|
intact.h(1)
|
|
intact.cx(0, 1)
|
|
intact.h(0)
|
|
intact.measure(0, 0)
|
|
|
|
broken = QuantumCircuit(2, 1)
|
|
broken.h(0)
|
|
broken.x(1)
|
|
broken.cx(0, 1)
|
|
broken.h(0)
|
|
broken.measure(0, 0)
|
|
|
|
{"intact": simulate_counts(intact, shots=256), "broken": simulate_counts(broken, shots=256)}
|
|
""",
|
|
},
|
|
],
|
|
"lab_quiz_a": DEUTSCH_LAB_QUIZ_A,
|
|
"lab_quiz_b": DEUTSCH_LAB_QUIZ_B,
|
|
"lab_debrief": """
|
|
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.
|
|
""",
|
|
"lab_reflections": [
|
|
"Which lab edit most improved your understanding of why the ancilla must be prepared as it is?",
|
|
"Write a short review note about one Deutsch-Jozsa oracle candidate that looks plausible but does not clearly expose its promise class.",
|
|
],
|
|
"problem_intro": """
|
|
These problems check whether you can keep the semantic burden of the Deutsch family in view when the wording changes. Treat each block like a miniature design review instead of a trivia quiz.
|
|
""",
|
|
"problem_how": """
|
|
Move slowly enough that you can say why the wrong answers remain tempting. If a wrong answer sounds attractive because it is shorter or more mysterious, that is exactly the weakness this notebook is trying to flush out.
|
|
""",
|
|
"problem_sets": DEUTSCH_PROBLEM_SETS,
|
|
"problem_case": """
|
|
One of the easiest failures in early algorithm notebooks is a kind of respectable-looking vagueness. The code runs. The diagram resembles the textbook. The counts look stable. But the writer has stopped being able to say what family of functions the oracle represents, which wire carries the verdict, or which step actually turns phase into evidence. This module refuses to accept that kind of vagueness as understanding. The point of the problems notebook is to force more exact language until the explanation becomes robust enough to survive later complexity.
|
|
""",
|
|
"problem_reflections": [
|
|
"Explain why the phrase 'the circuit just knows' is pedagogically dangerous in this module.",
|
|
"Write a short design memo defending why only the decision wire should be measured in a minimal Deutsch implementation.",
|
|
],
|
|
"problem_exit": """
|
|
Move on when you can describe the Deutsch family as a reusable oracle-and-interference pattern rather than as two isolated diagrams.
|
|
""",
|
|
"studio_intro": """
|
|
The studio turns the family into a design object. You are no longer only reading the mechanism. You are curating variants, defending what changed, and writing the kind of notes another engineer could actually review.
|
|
""",
|
|
"studio_brief": """
|
|
Produce a small oracle-design mini-portfolio. Include at least one minimal Deutsch case, one Deutsch-Jozsa case, and one deliberate ablation. For each, state the promise class, the reporting contract, and the causal reason the observed verdict should follow.
|
|
""",
|
|
"studio_sections": [
|
|
{
|
|
"heading": "Studio Prompt 1: Curate A Minimal Family",
|
|
"intro": """
|
|
Build a tiny family of promise-case circuits and make the family resemblance explicit. The goal is not volume. The goal is a clean taxonomy.
|
|
""",
|
|
"editable_code": DEUTSCH_ANCHOR,
|
|
"editable_title": "Studio 1: Minimal Deutsch Family",
|
|
"editable_instructions": "Turn one balanced and two constant variants into a cleanly explained family. Keep the oracle contract explicit in code comments or naming.",
|
|
},
|
|
{
|
|
"heading": "Studio Prompt 2: Scale Without Losing The Story",
|
|
"intro": """
|
|
Extend the same explanatory discipline to the Deutsch-Jozsa case. If the larger circuit makes your narrative weaker, simplify until the narrative becomes sharp again.
|
|
""",
|
|
"editable_code": DEUTSCH_DJ_EDITABLE,
|
|
"editable_title": "Studio 2: Deutsch-Jozsa Family",
|
|
"editable_instructions": "Create at least one balanced and one constant case and write down the invariant mechanism they share.",
|
|
},
|
|
{
|
|
"heading": "Studio Prompt 3: Ablation As Evidence",
|
|
"intro": """
|
|
Include one broken variant on purpose. A good studio notebook does not only show what works. It shows what fails and why, because that is how your causal understanding becomes reviewable.
|
|
""",
|
|
"editable_code": DEUTSCH_KICKBACK_EDITABLE,
|
|
"editable_title": "Studio 3: Broken Variant And Diagnosis",
|
|
"editable_instructions": "Break one preparation or interference step deliberately. Then write a diagnosis that names the missing mechanism.",
|
|
},
|
|
],
|
|
"studio_quiz": DEUTSCH_STUDIO_QUIZ,
|
|
"studio_debrief": """
|
|
A mature outcome in this studio is not a giant notebook. It is a compact family of circuits whose common logic is easy to explain and whose differences are intentional. If you can produce that here, the later algorithm modules have something disciplined to build on.
|
|
""",
|
|
"studio_reflections": [
|
|
"Which family resemblance between Deutsch and Deutsch-Jozsa feels strongest to you now, and why?",
|
|
"What was the most useful deliberate failure you introduced in the studio, and what did it reveal?",
|
|
"Write one paragraph defending your preferred oracle helper style for this family.",
|
|
"State one review criterion you would carry from this module into later algorithm notebooks.",
|
|
],
|
|
},
|
|
{
|
|
"dir": MODULE_02_DIR,
|
|
"lecture_title": "Bernstein-Vazirani and Structured Oracles",
|
|
"slug": "bv",
|
|
"lecture_intro": """
|
|
Bernstein-Vazirani is the next step because it takes the oracle language from the Deutsch family and gives it a richer internal structure. Instead of distinguishing a promise class like constant versus balanced, you now recover a hidden linear pattern. That shift matters pedagogically. The oracle is no longer just an example of query logic; it becomes a reusable way to encode structure. If you learn the module well, you stop seeing the circuit as a memorized diagram and start seeing it as a design pattern for extracting hidden linear information.
|
|
""",
|
|
"learning_objective": """
|
|
By the end of this lecture you should be able to explain how the hidden string appears as actual connectivity inside the oracle, why the final Hadamards recover that structure, and why bit-order discipline is not a cosmetic issue but part of the external contract of the notebook. You should also be able to compare two candidate oracle builders and say which one is more reviewable.
|
|
""",
|
|
"lecture_sections": [
|
|
"""
|
|
The most important conceptual move in Bernstein-Vazirani is to stop treating the secret string as a mysterious label attached after the fact. In the circuit, the string is implemented. Each secret bit decides whether a query wire participates in the parity-like interaction with the ancilla. That means the secret is embodied in the oracle's shape. This is a professional-design lesson as much as an algorithm lesson. Good code should make that embodiment visible enough that another engineer can audit it without reverse engineering an opaque indexing trick.
|
|
""",
|
|
"""
|
|
The ancilla preparation and the final Hadamard layer still play the same broad roles they played in the Deutsch family, but the middle is now more structured. The circuit is asking a wider question in one shot. The phase-sensitive ancilla lets the oracle write a linear pattern across many query branches, and the closing Hadamards decode that pattern back into the computational basis. Once you see that arc, the algorithm stops being a curiosity and becomes a design motif: encode structured information into phase, then decode it coherently.
|
|
""",
|
|
"""
|
|
Bit order becomes central here because the target result is itself a string. It is possible to build a correct oracle and still present the result badly if the mapping between qubit indices, circuit drawing order, and classical readout order is muddy. That is why this lecture treats reporting as part of the algorithm. If the notebook says the secret is 101 but the measurement contract is really returning that pattern in the opposite direction, the notebook is not professionally finished. It may still be mathematically salvageable, but the engineering surface is weak.
|
|
""",
|
|
"""
|
|
Another valuable feature of Bernstein-Vazirani is that it rewards family-level experimentation. Once you have a parameterized oracle helper, you can vary the secret string and watch what remains invariant. The preparation layer remains the same. The decoding layer remains the same. Only the internal routing pattern changes. This is precisely the kind of transfer you want in a serious lecture series. The learner should stop asking 'what is the BV circuit?' and start asking 'which parts of the BV family are stable, and which parts encode the task instance?'
|
|
""",
|
|
"""
|
|
That family perspective is also what keeps the module from becoming too toy-like. In real engineering work you rarely get to memorize one answer. You need a builder that scales across a class of inputs and still stays intelligible under review. Bernstein-Vazirani is a manageable place to practice that standard. The notebook can stay small while the design question becomes more serious: can you write a family builder that remains transparent about which structure is being encoded?
|
|
""",
|
|
"""
|
|
The module therefore sits at an important midpoint. It is still clean enough to live mostly in ideal mode, but it already behaves like real circuit engineering. There is a parameterized helper, a reporting contract, an auditable oracle body, and a clear difference between 'the circuit executed' and 'the circuit expressed the intended structured claim clearly.' That difference is exactly what the bundle is trying to teach.
|
|
""",
|
|
],
|
|
"anchor_intro": """
|
|
The anchor implementation below makes the secret string visible as routing structure. Read the code-to-diagram reference table first. Then edit the secret and the measurement order carefully enough that you can still defend what the printed bitstring means.
|
|
""",
|
|
"step_refs": BV_STEP_REFS,
|
|
"anchor_code": BV_ANCHOR,
|
|
"analysis_code": """
|
|
def bv_counts(secret: str) -> dict[str, int]:
|
|
circuit = QuantumCircuit(4, 3)
|
|
circuit.h([0, 1, 2])
|
|
circuit.x(3)
|
|
circuit.h(3)
|
|
for index, bit in enumerate(reversed(secret)):
|
|
if bit == "1":
|
|
circuit.cx(index, 3)
|
|
circuit.h([0, 1, 2])
|
|
circuit.measure([0, 1, 2], [0, 1, 2])
|
|
return simulate_counts(circuit, shots=256)
|
|
|
|
{secret: bv_counts(secret) for secret in ["000", "101", "110", "111"]}
|
|
""",
|
|
"lecture_quiz_a": BV_QUIZ_A,
|
|
"lecture_quiz_b": BV_QUIZ_B,
|
|
"lecture_review_sections": [
|
|
"""
|
|
A useful discipline in this module is to narrate the circuit from left to right in terms of burden. The preparation layer defines the question format. The oracle body encodes the secret structure. The final Hadamards decode that structure. The measurement layer reports the secret using a chosen ordering convention. If you can say that cleanly, the module is no longer just a set of gates. It is a compact engineering artifact.
|
|
""",
|
|
"""
|
|
Another discipline is to treat any bit-order confusion as a serious bug even if the fix is small. This may sound strict, but the strictness is what turns a notebook into professional training. Many subtle failures in real quantum workflows are really interface failures. They arise because someone knew the mathematics but could not preserve the mapping between representation layers. This is the safest module in which to learn that lesson.
|
|
""",
|
|
],
|
|
"lecture_reflections": [
|
|
"Explain why the hidden string in BV should be thought of as structure in the oracle rather than as a label added after execution.",
|
|
"Describe one way to make a BV builder more reviewable without changing its behavior.",
|
|
],
|
|
"lab_intro": """
|
|
The lab emphasizes three things: secret variation, bit-order discipline, and comparison between candidates that are behaviorally equivalent but structurally different. That is enough to make the module feel like engineering rather than recitation.
|
|
""",
|
|
"lab_protocol": """
|
|
Change one family parameter at a time. When you alter the secret, say which oracle edges should change. When you alter the reporting order, say which interface claim you are changing. When you alter the helper shape, say why the new builder is easier or harder to audit.
|
|
""",
|
|
"lab_sections": [
|
|
{
|
|
"heading": "Lab 1: Secret Variations",
|
|
"intro": """
|
|
Use the parameterized builder as a real family, not as a single example with a variable name. Try several secrets and force yourself to predict which CNOT pattern they should induce before you render the circuit.
|
|
""",
|
|
"step_refs": BV_STEP_REFS,
|
|
"editable_code": BV_ANCHOR,
|
|
"editable_title": "Lab 1: Secret Variations",
|
|
"editable_instructions": "Change the secret string and predict the oracle wiring and output bitstring before you run the cell.",
|
|
"after_code": """
|
|
def candidate(secret: str) -> dict[str, object]:
|
|
circuit = QuantumCircuit(4, 3)
|
|
circuit.h([0, 1, 2])
|
|
circuit.x(3)
|
|
circuit.h(3)
|
|
for index, bit in enumerate(reversed(secret)):
|
|
if bit == "1":
|
|
circuit.cx(index, 3)
|
|
circuit.h([0, 1, 2])
|
|
circuit.measure([0, 1, 2], [0, 1, 2])
|
|
return {"secret": secret, "counts": simulate_counts(circuit, shots=256)}
|
|
|
|
[candidate(secret) for secret in ["001", "010", "101", "111"]]
|
|
""",
|
|
},
|
|
{
|
|
"heading": "Lab 2: Bit-Order Discipline",
|
|
"intro": """
|
|
This lab isolates the most common reporting failure. Keep the oracle fixed and alter only how the query wires are mapped to classical bits. Then ask whether the result is still telling the truth in the form your notebook claims.
|
|
""",
|
|
"step_refs": None,
|
|
"editable_code": BV_ORDER_EDITABLE,
|
|
"editable_title": "Lab 2: Bit Order And Reporting",
|
|
"editable_instructions": "Experiment with measurement order, but never change it casually. Write down exactly which string convention the notebook is reporting.",
|
|
"after_code": """
|
|
canonical = QuantumCircuit(4, 3)
|
|
canonical.h([0, 1, 2])
|
|
canonical.x(3)
|
|
canonical.h(3)
|
|
canonical.cx(0, 3)
|
|
canonical.cx(1, 3)
|
|
canonical.h([0, 1, 2])
|
|
canonical.measure([0, 1, 2], [0, 1, 2])
|
|
|
|
reversed_map = QuantumCircuit(4, 3)
|
|
reversed_map.h([0, 1, 2])
|
|
reversed_map.x(3)
|
|
reversed_map.h(3)
|
|
reversed_map.cx(0, 3)
|
|
reversed_map.cx(1, 3)
|
|
reversed_map.h([0, 1, 2])
|
|
reversed_map.measure([0, 1, 2], [2, 1, 0])
|
|
|
|
{"canonical": simulate_counts(canonical, shots=256), "reversed_map": simulate_counts(reversed_map, shots=256)}
|
|
""",
|
|
},
|
|
{
|
|
"heading": "Lab 3: Candidate Comparison",
|
|
"intro": """
|
|
Compare a plain builder with one that adds a little extra presentational structure. The important question is not which one is shorter. It is which one makes the secret-routing logic easiest to audit without obscuring the global algorithmic pattern.
|
|
""",
|
|
"step_refs": None,
|
|
"editable_code": BV_ALTERNATIVE_EDITABLE,
|
|
"editable_title": "Lab 3: Candidate Comparison",
|
|
"editable_instructions": "Adjust the helper shape and decide whether the added structure clarifies or obscures the hidden-string logic.",
|
|
"after_code": """
|
|
lean = QuantumCircuit(4, 3)
|
|
lean.h([0, 1, 2])
|
|
lean.x(3)
|
|
lean.h(3)
|
|
lean.cx(0, 3)
|
|
lean.cx(2, 3)
|
|
lean.h([0, 1, 2])
|
|
lean.measure([0, 1, 2], [0, 1, 2])
|
|
|
|
structured = QuantumCircuit(4, 3)
|
|
structured.h([0, 1, 2])
|
|
structured.x(3)
|
|
structured.h(3)
|
|
structured.barrier()
|
|
structured.cx(0, 3)
|
|
structured.cx(2, 3)
|
|
structured.barrier()
|
|
structured.h([0, 1, 2])
|
|
structured.measure([0, 1, 2], [0, 1, 2])
|
|
|
|
{
|
|
"lean_counts": simulate_counts(lean, shots=256),
|
|
"structured_counts": simulate_counts(structured, shots=256),
|
|
"lean_depth": lean.depth(),
|
|
"structured_depth": structured.depth(),
|
|
}
|
|
""",
|
|
},
|
|
],
|
|
"lab_quiz_a": BV_LAB_QUIZ_A,
|
|
"lab_quiz_b": BV_LAB_QUIZ_B,
|
|
"lab_debrief": """
|
|
If the lab worked properly, the module should now feel less like a single algorithm and more like a family-level builder exercise. You should have seen that secret changes affect the oracle body, reporting-order changes affect the external contract, and helper-shape changes affect reviewability. Those are three different categories of edit. Learning to distinguish them is the real engineering gain.
|
|
""",
|
|
"lab_reflections": [
|
|
"Which bit-order experiment in the lab was most clarifying, and what did it teach you about interface design?",
|
|
"Write a short code-review note comparing the lean and structured BV candidates.",
|
|
],
|
|
"problem_intro": """
|
|
These problems test whether you can maintain family-level reasoning in the presence of tempting shortcuts. Treat them like short design-review scenarios rather than isolated exam items.
|
|
""",
|
|
"problem_how": """
|
|
The correct answers are the ones that preserve structure, interface clarity, and evidence all at once. If an option sounds attractive because it ignores one of those burdens, it is probably setting up a later failure.
|
|
""",
|
|
"problem_sets": BV_PROBLEM_SETS,
|
|
"problem_case": """
|
|
A recurring anti-pattern in quantum notebooks is the 'works for my secret' builder. It runs for one hand-picked example, but it is too opaque or too brittle to support variation. That anti-pattern is especially dangerous in a module like Bernstein-Vazirani because the whole point is that a clean family builder should exist. The problems notebook is therefore asking for something stronger than one successful run. It is asking whether your explanation survives secret changes, interface changes, and review pressure.
|
|
""",
|
|
"problem_reflections": [
|
|
"Explain why family-level testing is more persuasive than a single successful BV example.",
|
|
"Describe a minimal checklist you would use before trusting a hidden-string notebook written by someone else.",
|
|
],
|
|
"problem_exit": """
|
|
Move on when the phrase hidden string makes you think first of oracle structure and reporting contract, not of a memorized output.
|
|
""",
|
|
"studio_intro": """
|
|
The studio asks you to produce a small but serious hidden-string toolkit. This is the point where the module should start resembling real design work rather than lecture replay.
|
|
""",
|
|
"studio_brief": """
|
|
Build a compact notebook that recovers several secrets, defends a stable reporting convention, and compares at least two builder styles for auditability. Your aim is not maximal code. Your aim is a reliable family artifact.
|
|
""",
|
|
"studio_sections": [
|
|
{
|
|
"heading": "Studio Prompt 1: Secret-Recovery Library",
|
|
"intro": """
|
|
Build a secret-recovery helper that you would actually trust next week. The test is whether another reader can tell which secret bits induce which oracle interactions.
|
|
""",
|
|
"editable_code": BV_ANCHOR,
|
|
"editable_title": "Studio 1: Secret-Recovery Library",
|
|
"editable_instructions": "Generalize the builder across several secrets while keeping the oracle mapping readable.",
|
|
},
|
|
{
|
|
"heading": "Studio Prompt 2: Reporting Convention Memo",
|
|
"intro": """
|
|
Freeze one reporting convention and state it explicitly. Good notebooks do not hide bit order in implicit habits.
|
|
""",
|
|
"editable_code": BV_ORDER_EDITABLE,
|
|
"editable_title": "Studio 2: Reporting Convention",
|
|
"editable_instructions": "Choose a measurement mapping and write the convention clearly enough that another engineer would not misread the output.",
|
|
},
|
|
{
|
|
"heading": "Studio Prompt 3: Compare Two Builder Styles",
|
|
"intro": """
|
|
Compare a lean and a more structured candidate. The point is not that one style is always correct. The point is to learn how to justify the tradeoff in review language.
|
|
""",
|
|
"editable_code": BV_ALTERNATIVE_EDITABLE,
|
|
"editable_title": "Studio 3: Builder Comparison",
|
|
"editable_instructions": "Adjust helper structure, barriers, or naming, then defend which version is easier to audit and maintain.",
|
|
},
|
|
],
|
|
"studio_quiz": BV_STUDIO_QUIZ,
|
|
"studio_debrief": """
|
|
A good studio notebook in this module makes hidden structure feel ordinary and reviewable. If the secret still feels like a mysterious label rather than implemented structure, keep refining the builders and the prose until the structure is obvious.
|
|
""",
|
|
"studio_reflections": [
|
|
"What secret family did you use in the studio, and why was it a good stress test for your builder?",
|
|
"Which reporting convention did you choose, and how did you defend it?",
|
|
"What is the strongest review sentence you wrote in this studio?",
|
|
"Name one habit from the Deutsch family that carried forward unchanged into BV.",
|
|
],
|
|
},
|
|
{
|
|
"dir": MODULE_03_DIR,
|
|
"lecture_title": "QFT and Periodic Structure",
|
|
"slug": "qft",
|
|
"lecture_intro": """
|
|
The QFT module marks a major transition in the course because the circuit can no longer be understood as simple compute-then-measure logic. The point of the transform is to reorganize phase information into a different basis. That makes it more abstract, but it also makes it more reusable. Many later algorithms rely on QFT-style reasoning, so the lecture has to do two jobs at once: keep the mathematics concrete enough for a beginner who is still growing, and make the transform explicit enough that it becomes a design tool instead of a magic box.
|
|
""",
|
|
"learning_objective": """
|
|
By the end of this lecture you should be able to explain a small QFT as a basis-change circuit built from a deliberate ladder of controlled phases and swaps, verify a hand-written implementation using statevector or inverse checks, and defend a small approximation as a tradeoff rather than as an arbitrary gate deletion.
|
|
""",
|
|
"lecture_sections": [
|
|
"""
|
|
The safest way to begin the QFT is to reject the urge to treat it as a name that excuses vagueness. In professional work, a named transform still has to be rendered into explicit design choices. Which qubits are involved? Which controlled phases appear? Why are the angles what they are? What ordering convention do the swaps enforce? A world-class course cannot let the learner glide past those questions just because the transform is famous. The entire module is built to keep the famous thing inspectable.
|
|
""",
|
|
"""
|
|
The phrase basis change is the most useful first handle. It tells you that the circuit is not trying to compute a classical answer and then measure it directly. It is trying to express the same state information in a new coordinate language where periodic or phase relationships become easier to read. That phrase also tells you what kind of evidence to seek. Basis-change circuits are poorly served by naive count histograms alone. You need statevector pictures, recovery checks, or other forms of structural verification.
|
|
""",
|
|
"""
|
|
The controlled-phase ladder is the conceptual center of the transform. Each step adds a graded phase relationship conditioned on another wire, so the transform accumulates information with different scales of influence. This is why small-angle terms matter even when they look visually minor. They are part of the representational contract. Later, when approximation becomes relevant, you may decide that some of those small contributions are expendable under a cost budget, but that decision only becomes meaningful once you understand what is being discarded.
|
|
""",
|
|
"""
|
|
Swaps deserve equal honesty. Many beginner explanations hide them in a footnote about bit reversal. In this course, swaps are part of the interface. They state which wire order the rest of the notebook should interpret as the transform output. That matters because every later comparison, inverse check, or algorithm embedding depends on stable conventions. A professional designer does not shrug at output ordering and promise to remember it later.
|
|
""",
|
|
"""
|
|
The QFT is also the first place where explicit verification habits become non-negotiable. A rendered circuit that resembles the textbook does not prove that the implementation is correct. A small sign error, angle error, or ordering mistake can leave you with a circuit that looks authoritative while being wrong in the only way that matters. That is why this lecture keeps pairing circuit graphics with statevector evidence and inverse composition. A serious notebook must connect representation to verification, not leave them as separate worlds.
|
|
""",
|
|
"""
|
|
Finally, the QFT is a perfect place to introduce principled approximation. If you remove a small-angle term, you are not merely simplifying the diagram. You are making a resource tradeoff. The tradeoff can be good, but only if it is named and checked. This attitude will matter later in hardware-aware and noisy settings. Approximation should never mean 'I removed a gate because the picture was crowded.' It should mean 'I removed a specific contribution for a stated reason and I measured the cost.'
|
|
""",
|
|
],
|
|
"anchor_intro": """
|
|
The anchor circuit below is intentionally explicit. No helper library call hides the transform. Read the marker table, inspect the circuit, and then verify it with the cells that follow. This module is about making a canonical transform readable, not about making it short.
|
|
""",
|
|
"step_refs": QFT_STEP_REFS,
|
|
"anchor_code": QFT_ANCHOR,
|
|
"analysis_code": """
|
|
def qft3() -> QuantumCircuit:
|
|
circuit = QuantumCircuit(3, name="qft3")
|
|
circuit.h(2)
|
|
circuit.cp(pi / 2, 1, 2)
|
|
circuit.cp(pi / 4, 0, 2)
|
|
circuit.h(1)
|
|
circuit.cp(pi / 2, 0, 1)
|
|
circuit.h(0)
|
|
circuit.swap(0, 2)
|
|
return circuit
|
|
|
|
basis_one = QuantumCircuit(3)
|
|
basis_one.x(0)
|
|
transformed = basis_one.compose(qft3())
|
|
probs = statevector_probabilities(transformed)
|
|
ax = plot_probabilities(probs, title="QFT Of |001>")
|
|
ax.figure
|
|
""",
|
|
"lecture_quiz_a": QFT_QUIZ_A,
|
|
"lecture_quiz_b": QFT_QUIZ_B,
|
|
"lecture_review_sections": [
|
|
"""
|
|
A good self-check in this module is to ask whether you can point to each gate and classify its role. If a gate is present only because 'QFT has one of those,' your understanding is still too inert. The lecture is trying to replace inert recognition with role-aware reading.
|
|
""",
|
|
"""
|
|
Another good self-check is whether you can defend a verification method. If you know the picture but do not know whether a statevector view, an inverse-composition check, or a sampled histogram is appropriate for the question at hand, then the transform is still more symbol than tool. This module wants the opposite outcome. It wants the transform to become something you can test and discuss.
|
|
""",
|
|
],
|
|
"lecture_reflections": [
|
|
"Explain why basis-change language is more useful than speedup language for learning the QFT.",
|
|
"Write one paragraph defending why the swap layer should be treated as part of the interface contract.",
|
|
],
|
|
"lab_intro": """
|
|
The lab focuses on explicit construction, verification, and controlled approximation. Unlike the previous modules, some of the most useful evidence will be statevector-based rather than count-based. That is deliberate.
|
|
""",
|
|
"lab_protocol": """
|
|
Change one structural feature at a time and pair every change with a verification move. If you adjust an angle, say what contribution you are changing. If you remove a swap, say which ordering convention you are abandoning. If you approximate the transform, say how you intend to observe the cost.
|
|
""",
|
|
"lab_sections": [
|
|
{
|
|
"heading": "Lab 1: Build The Transform Explicitly",
|
|
"intro": """
|
|
Start by editing the explicit QFT itself. The goal is not speed. The goal is to learn how each visible region contributes to the full transform and to make the diagram readable enough that you can review it without hiding behind the name QFT.
|
|
""",
|
|
"step_refs": QFT_STEP_REFS,
|
|
"editable_code": QFT_ANCHOR,
|
|
"editable_title": "Lab 1: Explicit QFT Builder",
|
|
"editable_instructions": "Adjust one phase angle or swap at a time and explain which part of the interface or representation you changed.",
|
|
"after_code": """
|
|
def qft3() -> QuantumCircuit:
|
|
circuit = QuantumCircuit(3, name="qft3")
|
|
circuit.h(2)
|
|
circuit.cp(pi / 2, 1, 2)
|
|
circuit.cp(pi / 4, 0, 2)
|
|
circuit.h(1)
|
|
circuit.cp(pi / 2, 0, 1)
|
|
circuit.h(0)
|
|
circuit.swap(0, 2)
|
|
return circuit
|
|
|
|
ax = draw_circuit(qft3())
|
|
ax
|
|
""",
|
|
},
|
|
{
|
|
"heading": "Lab 2: Verify With Recovery",
|
|
"intro": """
|
|
Use QFT followed by inverse QFT as a recovery pattern. This is a disciplined way to verify that your hand-written transform is not merely plausible-looking. A transform that cannot recover cleanly under its own inverse needs explanation.
|
|
""",
|
|
"step_refs": None,
|
|
"editable_code": QFT_RECOVERY_EDITABLE,
|
|
"editable_title": "Lab 2: QFT Plus Inverse Recovery",
|
|
"editable_instructions": "Change the prepared basis state or slightly edit the transform. Then decide whether the recovery still matches the story you think the circuit is telling.",
|
|
"after_code": """
|
|
def qft3() -> QuantumCircuit:
|
|
circuit = QuantumCircuit(3, name="qft3")
|
|
circuit.h(2)
|
|
circuit.cp(pi / 2, 1, 2)
|
|
circuit.cp(pi / 4, 0, 2)
|
|
circuit.h(1)
|
|
circuit.cp(pi / 2, 0, 1)
|
|
circuit.h(0)
|
|
circuit.swap(0, 2)
|
|
return circuit
|
|
|
|
recovered = QuantumCircuit(3, 3)
|
|
recovered.x(0)
|
|
recovered.compose(qft3(), inplace=True)
|
|
recovered.compose(qft3().inverse(), inplace=True)
|
|
recovered.measure([0, 1, 2], [0, 1, 2])
|
|
simulate_counts(recovered, shots=256)
|
|
""",
|
|
},
|
|
{
|
|
"heading": "Lab 3: Approximation Tradeoff",
|
|
"intro": """
|
|
Now remove the smallest-angle interaction and inspect the result like an engineer. The aim is not to decide that approximation is always good or always bad. The aim is to learn how to talk about the tradeoff in concrete, local terms.
|
|
""",
|
|
"step_refs": None,
|
|
"editable_code": QFT_APPROX_EDITABLE,
|
|
"editable_title": "Lab 3: Approximate QFT",
|
|
"editable_instructions": "Toggle the smallest-angle term and explain what structural simplification you gained and what representational burden you may have lost.",
|
|
"after_code": """
|
|
def qft3(drop_smallest_angle: bool) -> QuantumCircuit:
|
|
circuit = QuantumCircuit(3, name="qft3")
|
|
circuit.h(2)
|
|
circuit.cp(pi / 2, 1, 2)
|
|
if not drop_smallest_angle:
|
|
circuit.cp(pi / 4, 0, 2)
|
|
circuit.h(1)
|
|
circuit.cp(pi / 2, 0, 1)
|
|
circuit.h(0)
|
|
circuit.swap(0, 2)
|
|
return circuit
|
|
|
|
basis_one = QuantumCircuit(3)
|
|
basis_one.x(0)
|
|
full_probs = statevector_probabilities(basis_one.compose(qft3(False)))
|
|
approx_probs = statevector_probabilities(basis_one.compose(qft3(True)))
|
|
{"full": full_probs, "approx": approx_probs}
|
|
""",
|
|
},
|
|
],
|
|
"lab_quiz_a": QFT_LAB_QUIZ_A,
|
|
"lab_quiz_b": QFT_LAB_QUIZ_B,
|
|
"lab_debrief": """
|
|
The important outcome of the lab is not that you can rewrite one small QFT from memory. It is that you now have habits for reading, checking, and selectively simplifying basis-change circuits. Those habits are exactly what later algorithmic and professional modules will need.
|
|
""",
|
|
"lab_reflections": [
|
|
"Which verification move felt most convincing in this lab, and why?",
|
|
"Write a short engineering note defending either the full or approximate 3-qubit QFT for a small local study.",
|
|
],
|
|
"problem_intro": """
|
|
These problems check whether you can keep the QFT grounded in explicit roles instead of slipping back into name-based mystique.
|
|
""",
|
|
"problem_how": """
|
|
Whenever an answer sounds appealing because it is vague, reject it. This module improves only if you become more precise about basis change, phase structure, ordering, and verification.
|
|
""",
|
|
"problem_sets": QFT_PROBLEM_SETS,
|
|
"problem_case": """
|
|
The most common QFT failure mode in beginner material is reverent opacity. The transform is introduced with respect, but not with enough operational clarity. The learner comes away knowing that the QFT is important without knowing how to inspect, verify, or modify a small implementation. This problems notebook pushes directly against that failure. Importance without inspectability is not yet useful knowledge.
|
|
""",
|
|
"problem_reflections": [
|
|
"Explain why a beautiful circuit diagram is not enough evidence for a correct QFT implementation.",
|
|
"Describe one concrete reason an engineer might choose a small approximate-QFT variant.",
|
|
],
|
|
"problem_exit": """
|
|
Move on when you can read a 3-qubit QFT as a deliberate basis-change circuit and defend how you would verify it.
|
|
""",
|
|
"studio_intro": """
|
|
The studio asks you to turn the QFT from a named transform into a defended design artifact. That means explicit structure, explicit verification, and explicit tradeoffs.
|
|
""",
|
|
"studio_brief": """
|
|
Build a mini-transform notebook that includes one explicit full QFT, one verification workflow, and one approximation discussion. Your job is not only to make it work. Your job is to make it auditable.
|
|
""",
|
|
"studio_sections": [
|
|
{
|
|
"heading": "Studio Prompt 1: Explicit Transform Notebook",
|
|
"intro": """
|
|
Write the transform out explicitly enough that a reviewer could inspect every gate without guessing which part is essential.
|
|
""",
|
|
"editable_code": QFT_ANCHOR,
|
|
"editable_title": "Studio 1: Explicit Small QFT",
|
|
"editable_instructions": "Refine the explicit builder until every gate has a role you can explain clearly in prose.",
|
|
},
|
|
{
|
|
"heading": "Studio Prompt 2: Recovery And Ordering",
|
|
"intro": """
|
|
Pair the transform with an inverse or other verification strategy, and make sure your ordering convention is stated rather than assumed.
|
|
""",
|
|
"editable_code": QFT_RECOVERY_EDITABLE,
|
|
"editable_title": "Studio 2: Recovery Notebook",
|
|
"editable_instructions": "Choose a prepared state, verify recovery, and write down the ordering convention you are using.",
|
|
},
|
|
{
|
|
"heading": "Studio Prompt 3: Approximation Memo",
|
|
"intro": """
|
|
Compare full and approximate variants as if you had to justify the choice to another engineer working under a depth budget.
|
|
""",
|
|
"editable_code": QFT_APPROX_EDITABLE,
|
|
"editable_title": "Studio 3: Approximation Memo",
|
|
"editable_instructions": "Create a full and a simplified variant, then defend the simplification with concrete evidence and scope limits.",
|
|
},
|
|
],
|
|
"studio_quiz": QFT_STUDIO_QUIZ,
|
|
"studio_debrief": """
|
|
A good studio artifact in this module feels restrained and rigorous. It does not rely on fame. It makes the transform inspectable, testable, and discussable. That is the standard worth carrying forward.
|
|
""",
|
|
"studio_reflections": [
|
|
"What part of the QFT became most legible to you only after writing it out explicitly?",
|
|
"Which verification workflow did you choose in the studio, and why was it appropriate?",
|
|
"How did you describe the tradeoff in your approximate-QFT memo?",
|
|
"Name one later band where you expect this QFT vocabulary to matter directly.",
|
|
],
|
|
},
|
|
{
|
|
"dir": MODULE_04_DIR,
|
|
"lecture_title": "Grover and Amplitude Amplification",
|
|
"slug": "grover",
|
|
"lecture_intro": """
|
|
Grover is the last algorithmic-design module in the band because it forces several earlier ideas to operate together at once. You need oracle thinking from the Deutsch family, family-level builder discipline from Bernstein-Vazirani, and structured reasoning about repeated transformations that is compatible with the QFT's emphasis on mechanism. Grover is often taught as a famous speedup. Here it is taught as a composed design: build a phase-marking oracle, build a diffuser that acts as a reflection about the mean, decide how many times to apply the pair, and defend the resulting circuit under review.
|
|
""",
|
|
"learning_objective": """
|
|
By the end of this lecture you should be able to explain the distinct roles of the oracle and diffuser, describe amplitude amplification as a controlled rotation rather than vague strengthening, choose an iteration count for a tiny search space, and critique a Grover notebook that hides target-marking logic behind unreadable helper code.
|
|
""",
|
|
"lecture_sections": [
|
|
"""
|
|
The first mistake this module tries to prevent is treating Grover as a one-word explanation. Saying that Grover performs search is true but insufficient. Search is the task. The circuit mechanism is more specific: one block marks a target by phase, another reflects amplitudes about their mean, and repeating the pair rotates the state toward the marked candidate. That sentence is already much more useful than the word search alone because it names the actual design burdens. The rest of the notebook makes those burdens explicit in code.
|
|
""",
|
|
"""
|
|
Separating the oracle and diffuser is pedagogically essential. If you collapse them into one opaque helper, the routine will still run but the student loses the ability to inspect which block chooses the target and which block performs amplification. This course treats that loss as serious. When a later capstone asks you to defend a design, you will need to say whether a problem came from target-marking logic, amplification logic, or iteration choice. Grover is the module where that separation becomes normal.
|
|
""",
|
|
"""
|
|
The geometric language of rotation is not decorative either. It explains why one more iteration is not automatically better. In a small search space, one iteration may already place most of the amplitude on the target. A second iteration can overshoot. That is why the module stays intentionally small. On two qubits, the effect is easy to see, easy to count, and easy to connect back to design judgment. Small systems are where mechanism becomes visible rather than drowned by scale.
|
|
""",
|
|
"""
|
|
Oracle design remains central here. The target is implemented through phase marking, often with X wrappers around a central controlled-phase structure. The details are local but meaningful. Change the wrappers and you change which state is marked. Write the wrappers unclearly and you make the circuit hard to review even if the counts happen to peak on the right outcome. This is exactly the kind of difference between execution and engineering that the whole course is trying to teach.
|
|
""",
|
|
"""
|
|
The diffuser then deserves its own explanation. It is not just a block that comes after the oracle because the textbook says so. It is the block that reflects amplitudes about their average and thereby turns a phase-marked distinction into a measurement advantage. If that sentence feels too abstract, the lab will make it concrete. But the lecture still has to say it plainly, because without that meaning the diffuser becomes another memorized shape with no transfer value.
|
|
""",
|
|
"""
|
|
Grover therefore closes the algorithmic-design band on the right note. It is still small enough to study locally, yet rich enough to require oracle reasoning, helper discipline, geometric explanation, and explicit design tradeoffs. By the time you leave the bundle, the phrase algorithmic design should feel much less like an aspiration and much more like a practiced habit.
|
|
""",
|
|
],
|
|
"anchor_intro": """
|
|
The anchor circuit below uses a two-qubit search space so that target marking, diffusion, and iteration choice all remain visible. Read the reference table first, then edit the target and iteration choices carefully enough that you can still explain the mechanism in plain language.
|
|
""",
|
|
"step_refs": GROVER_STEP_REFS,
|
|
"anchor_code": GROVER_ANCHOR,
|
|
"analysis_code": """
|
|
def grover_run(target: str, iterations: int) -> dict[str, object]:
|
|
def grover_oracle(target: str) -> QuantumCircuit:
|
|
oracle = QuantumCircuit(2, name=f"oracle_{target}")
|
|
if target[0] == "0":
|
|
oracle.x(1)
|
|
if target[1] == "0":
|
|
oracle.x(0)
|
|
oracle.cz(0, 1)
|
|
if target[1] == "0":
|
|
oracle.x(0)
|
|
if target[0] == "0":
|
|
oracle.x(1)
|
|
return oracle
|
|
|
|
def diffuser() -> QuantumCircuit:
|
|
circuit = QuantumCircuit(2, name="diffuser")
|
|
circuit.h([0, 1])
|
|
circuit.x([0, 1])
|
|
circuit.cz(0, 1)
|
|
circuit.x([0, 1])
|
|
circuit.h([0, 1])
|
|
return circuit
|
|
|
|
circuit = QuantumCircuit(2, 2)
|
|
circuit.h([0, 1])
|
|
for _ in range(iterations):
|
|
circuit.compose(grover_oracle(target), inplace=True)
|
|
circuit.compose(diffuser(), inplace=True)
|
|
circuit.measure([0, 1], [0, 1])
|
|
return {"target": target, "iterations": iterations, "counts": simulate_counts(circuit, shots=256)}
|
|
|
|
[grover_run("11", iterations) for iterations in [0, 1, 2]]
|
|
""",
|
|
"lecture_quiz_a": GROVER_QUIZ_A,
|
|
"lecture_quiz_b": GROVER_QUIZ_B,
|
|
"lecture_review_sections": [
|
|
"""
|
|
A strong self-check in this module is whether you can explain a bad result without panicking. If a second iteration weakens the target peak, that is not bizarre behavior. It is exactly what the geometric story predicts in a small search space. Modules become professional only when surprising outputs can be narrated rather than merely observed.
|
|
""",
|
|
"""
|
|
Another strong self-check is whether your oracle helper remains readable across target changes. If changing the target string feels like spelunking through index tricks, the builder is not yet carrying its design burden well enough. Grover makes that weakness visible very quickly, which is one reason it is such a good teaching module.
|
|
""",
|
|
],
|
|
"lecture_reflections": [
|
|
"Explain the difference between oracle marking and diffusion in Grover without collapsing them into one vague sentence.",
|
|
"Why is iteration count an engineering choice rather than a fixed ritual in this module?",
|
|
],
|
|
"lab_intro": """
|
|
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.
|
|
""",
|
|
"lab_protocol": """
|
|
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.
|
|
""",
|
|
"lab_sections": [
|
|
{
|
|
"heading": "Lab 1: Target Changes",
|
|
"intro": """
|
|
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.
|
|
""",
|
|
"step_refs": GROVER_STEP_REFS,
|
|
"editable_code": GROVER_ANCHOR,
|
|
"editable_title": "Lab 1: Target Changes",
|
|
"editable_instructions": "Change the target string and verify that the X-wrapper logic marks the state you intended before you trust the counts.",
|
|
"after_code": """
|
|
def run(target: str) -> dict[str, int]:
|
|
circuit = QuantumCircuit(2, 2)
|
|
circuit.h([0, 1])
|
|
if target[0] == "0":
|
|
circuit.x(1)
|
|
if target[1] == "0":
|
|
circuit.x(0)
|
|
circuit.cz(0, 1)
|
|
if target[1] == "0":
|
|
circuit.x(0)
|
|
if target[0] == "0":
|
|
circuit.x(1)
|
|
circuit.h([0, 1])
|
|
circuit.x([0, 1])
|
|
circuit.cz(0, 1)
|
|
circuit.x([0, 1])
|
|
circuit.h([0, 1])
|
|
circuit.measure([0, 1], [0, 1])
|
|
return simulate_counts(circuit, shots=256)
|
|
|
|
{target: run(target) for target in ["00", "01", "10", "11"]}
|
|
""",
|
|
},
|
|
{
|
|
"heading": "Lab 2: Iteration Comparison",
|
|
"intro": """
|
|
Next compare one and two iterations explicitly. This is where the rotation language should stop feeling decorative and start feeling predictive.
|
|
""",
|
|
"step_refs": None,
|
|
"editable_code": GROVER_ITERATION_EDITABLE,
|
|
"editable_title": "Lab 2: Iteration Comparison",
|
|
"editable_instructions": "Vary the iteration count and explain whether the target amplitude should still be moving toward or past the intended peak.",
|
|
"after_code": """
|
|
def grover_counts(iterations: int) -> dict[str, int]:
|
|
circuit = QuantumCircuit(2, 2)
|
|
circuit.h([0, 1])
|
|
for _ in range(iterations):
|
|
circuit.cz(0, 1)
|
|
circuit.h([0, 1])
|
|
circuit.x([0, 1])
|
|
circuit.cz(0, 1)
|
|
circuit.x([0, 1])
|
|
circuit.h([0, 1])
|
|
circuit.measure([0, 1], [0, 1])
|
|
return simulate_counts(circuit, shots=256)
|
|
|
|
{iterations: grover_counts(iterations) for iterations in [0, 1, 2]}
|
|
""",
|
|
},
|
|
{
|
|
"heading": "Lab 3: Oracle Readability",
|
|
"intro": """
|
|
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.
|
|
""",
|
|
"step_refs": None,
|
|
"editable_code": GROVER_ITERATION_EDITABLE,
|
|
"editable_title": "Lab 3: Oracle Readability",
|
|
"editable_instructions": "Refactor the target-marking helper and decide whether the new version is easier or harder to review across target changes.",
|
|
"after_code": """
|
|
one_iteration = QuantumCircuit(2, 2)
|
|
one_iteration.h([0, 1])
|
|
one_iteration.cz(0, 1)
|
|
one_iteration.h([0, 1])
|
|
one_iteration.x([0, 1])
|
|
one_iteration.cz(0, 1)
|
|
one_iteration.x([0, 1])
|
|
one_iteration.h([0, 1])
|
|
one_iteration.measure([0, 1], [0, 1])
|
|
|
|
two_iterations = QuantumCircuit(2, 2)
|
|
two_iterations.h([0, 1])
|
|
for _ in range(2):
|
|
two_iterations.cz(0, 1)
|
|
two_iterations.h([0, 1])
|
|
two_iterations.x([0, 1])
|
|
two_iterations.cz(0, 1)
|
|
two_iterations.x([0, 1])
|
|
two_iterations.h([0, 1])
|
|
two_iterations.measure([0, 1], [0, 1])
|
|
|
|
{
|
|
"one_iteration": simulate_counts(one_iteration, shots=256),
|
|
"two_iterations": simulate_counts(two_iterations, shots=256),
|
|
}
|
|
""",
|
|
},
|
|
],
|
|
"lab_quiz_a": GROVER_LAB_QUIZ_A,
|
|
"lab_quiz_b": GROVER_LAB_QUIZ_B,
|
|
"lab_debrief": """
|
|
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.
|
|
""",
|
|
"lab_reflections": [
|
|
"What did the one-versus-two iteration comparison teach you about the geometric story of Grover?",
|
|
"Write a short review note about one oracle helper that would be too opaque for serious use.",
|
|
],
|
|
"problem_intro": """
|
|
These problems test whether you can maintain a clear distinction between task, mechanism, and design tradeoff in a Grover notebook.
|
|
""",
|
|
"problem_how": """
|
|
Reject answers that sound strong only because they are famous or dramatic. The best answers keep the routine decomposed into auditable parts and attach claims to evidence.
|
|
""",
|
|
"problem_sets": GROVER_PROBLEM_SETS,
|
|
"problem_case": """
|
|
Grover is especially vulnerable to prestige-based learning. Because the routine is widely known, learners often assume they understand it after seeing one polished diagram. But a polished diagram does not answer the questions an engineer will face: how is the target encoded, how readable is the oracle helper, how many iterations are appropriate for this search space, and what evidence supports that choice? The problems notebook asks those questions directly so the prestige does not mask the mechanism.
|
|
""",
|
|
"problem_reflections": [
|
|
"Explain why a strong Grover notebook must separate oracle logic from diffuser logic.",
|
|
"Describe the evidence you would use to justify an iteration count in a tiny local search problem.",
|
|
],
|
|
"problem_exit": """
|
|
Move on when you can treat Grover as a composed routine with explicit roles and tradeoffs, not as a famous diagram you happened to reproduce.
|
|
""",
|
|
"studio_intro": """
|
|
The studio closes the algorithmic-design band by asking for a real search-design memo. This is where the earlier lecture and lab work should condense into a small piece of professional reasoning.
|
|
""",
|
|
"studio_brief": """
|
|
Build a two-qubit Grover design study that compares at least two targets and at least two iteration choices, then write a recommendation that defends the oracle style, diffuser reuse, and final iteration count.
|
|
""",
|
|
"studio_sections": [
|
|
{
|
|
"heading": "Studio Prompt 1: Target Family",
|
|
"intro": """
|
|
Show that the search routine works as a family over multiple marked states and that your target-marking code remains readable across those changes.
|
|
""",
|
|
"editable_code": GROVER_ANCHOR,
|
|
"editable_title": "Studio 1: Target Family",
|
|
"editable_instructions": "Build a small family of target-marked searches and defend the readability of your oracle implementation.",
|
|
},
|
|
{
|
|
"heading": "Studio Prompt 2: Iteration Memo",
|
|
"intro": """
|
|
Compare one and two iterations as if you had to advise another engineer which to keep for this search size.
|
|
""",
|
|
"editable_code": GROVER_ITERATION_EDITABLE,
|
|
"editable_title": "Studio 2: Iteration Memo",
|
|
"editable_instructions": "Collect evidence for at least two iteration counts and write a recommendation tied to the small-search geometry.",
|
|
},
|
|
{
|
|
"heading": "Studio Prompt 3: Oracle Review",
|
|
"intro": """
|
|
Rewrite or refactor the oracle helper until you are satisfied that a reviewer could check it quickly without guessing which state is marked.
|
|
""",
|
|
"editable_code": GROVER_ITERATION_EDITABLE,
|
|
"editable_title": "Studio 3: Oracle Review",
|
|
"editable_instructions": "Refactor the target-marking code for auditability and explain why the new version is preferable.",
|
|
},
|
|
],
|
|
"studio_quiz": GROVER_STUDIO_QUIZ,
|
|
"studio_debrief": """
|
|
A successful studio notebook here reads like a design note rather than like a performance. It should be compact, explicit, and willing to defend why one candidate was chosen over another. That is the right note on which to leave the algorithmic-design band.
|
|
""",
|
|
"studio_reflections": [
|
|
"Which target family did you compare, and what did that reveal about your oracle helper?",
|
|
"What iteration recommendation did you make, and what evidence mattered most?",
|
|
"How did you explain the diffuser in your own words in the final notebook?",
|
|
"Name one habit from this algorithmic-design band that should carry into the professional-design band.",
|
|
],
|
|
},
|
|
]
|
|
|
|
|
|
def build_lecture(module: dict) -> dict:
|
|
cells = [
|
|
markdown_cell(f"# {module['lecture_title']} Lecture"),
|
|
markdown_cell(module["lecture_intro"]),
|
|
markdown_cell(f"## Learning Objective\n\n{module['learning_objective']}"),
|
|
]
|
|
for section in module["lecture_sections"]:
|
|
cells.append(markdown_cell(section))
|
|
|
|
cells.extend(
|
|
[
|
|
code_cell(SETUP),
|
|
code_cell(COMMON_IMPORTS),
|
|
markdown_cell(f"## Code-To-Diagram Anchor\n\n{module['anchor_intro']}"),
|
|
code_cell(f"step_reference_table({module['step_refs']!r})"),
|
|
editable_lab_code(
|
|
module["anchor_code"],
|
|
title=f"{module['lecture_title']} Anchor",
|
|
instructions="Edit one causal region at a time. Use the reference table to keep code changes tied to circuit meaning.",
|
|
),
|
|
code_cell(module["analysis_code"]),
|
|
quiz_code(module["lecture_quiz_a"], "Lecture Checkpoint A"),
|
|
]
|
|
)
|
|
|
|
for section in module["lecture_review_sections"]:
|
|
cells.append(markdown_cell(section))
|
|
|
|
cells.extend(
|
|
[
|
|
markdown_cell(
|
|
"""
|
|
## Reading Discipline For This Module
|
|
|
|
A serious lecture notebook has to teach more than recognition. It has to teach what to look at, what to ask, and what kind of evidence would count as a meaningful check. That is why this module keeps repeating a small number of disciplined questions. Which region prepares the state or question format? Which region encodes the task-specific structure? Which region translates that hidden structure into evidence you can actually read? And which parts of the notebook are presentation choices rather than mechanism? Those questions may sound repetitive, but repetition is useful here because algorithmic circuits become opaque very quickly when the learner loses the habit of dividing them into roles.
|
|
|
|
Another reason for this slower lecture style is that professional design does not tolerate admiration as a substitute for analysis. It is perfectly possible to feel impressed by an algorithm, reproduce the overall diagram, and still be unable to explain what would break if one line changed. This course is trying to build the opposite habit. A mature notebook reader should be able to look at a circuit region and say what burden it carries, what evidence would test it, and what kind of mistake would falsify the current explanation. If that standard feels demanding, that is appropriate. The goal is to create designers, not spectators.
|
|
"""
|
|
),
|
|
markdown_cell(
|
|
"""
|
|
## Forward Link
|
|
|
|
One reason these notebooks are written so densely is that later professional-design modules will assume this vocabulary is stable. When you later compare transpiled candidates, argue about noise sensitivity, or defend a capstone recommendation, you will not have time to rediscover what an oracle contract, reporting convention, controlled phase, or iteration choice means. The language has to be ready. That is why this lecture insists on precision now. The details are not there to slow you down forever. They are there so later speed is built on something trustworthy.
|
|
"""
|
|
),
|
|
quiz_code(module["lecture_quiz_b"], "Lecture Checkpoint B"),
|
|
reflection_code(module["lecture_reflections"][0]),
|
|
reflection_code(module["lecture_reflections"][1]),
|
|
markdown_cell(
|
|
"## Mastery Gate\n\nLeave this lecture only when you can explain the mechanism line by line, not just recognize the diagram."
|
|
),
|
|
]
|
|
)
|
|
return notebook(cells)
|
|
|
|
|
|
def build_lab(module: dict) -> dict:
|
|
cells = [
|
|
markdown_cell(f"# {module['lecture_title']} Lab"),
|
|
markdown_cell(module["lab_intro"]),
|
|
markdown_cell(f"## Lab Protocol\n\n{module['lab_protocol']}"),
|
|
code_cell(SETUP),
|
|
code_cell(COMMON_IMPORTS),
|
|
]
|
|
|
|
first_lab = True
|
|
for section in module["lab_sections"]:
|
|
cells.append(markdown_cell(f"## {section['heading']}\n\n{section['intro']}"))
|
|
if section["step_refs"] is not None:
|
|
cells.append(code_cell(f"step_reference_table({section['step_refs']!r})"))
|
|
cells.append(
|
|
editable_lab_code(
|
|
section["editable_code"],
|
|
title=section["editable_title"],
|
|
instructions=section["editable_instructions"],
|
|
)
|
|
)
|
|
if section["after_code"]:
|
|
cells.append(code_cell(section["after_code"]))
|
|
if first_lab:
|
|
cells.append(quiz_code(module["lab_quiz_a"], "Lab Checkpoint A"))
|
|
cells.append(reflection_code(module["lab_reflections"][0]))
|
|
first_lab = False
|
|
|
|
cells.extend(
|
|
[
|
|
quiz_code(module["lab_quiz_b"], "Lab Checkpoint B"),
|
|
markdown_cell(f"## Lab Debrief\n\n{module['lab_debrief']}"),
|
|
markdown_cell(
|
|
"""
|
|
## Why The Lab Is Slower Than A Tutorial
|
|
|
|
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.
|
|
"""
|
|
),
|
|
markdown_cell(
|
|
"""
|
|
## Prediction Ledger
|
|
|
|
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.
|
|
"""
|
|
),
|
|
reflection_code(module["lab_reflections"][1]),
|
|
reflection_code("Write one additional prediction habit you want to carry into later modules."),
|
|
]
|
|
)
|
|
return notebook(cells)
|
|
|
|
|
|
def build_problems(module: dict) -> dict:
|
|
cells = [
|
|
markdown_cell(f"# {module['lecture_title']} Problems"),
|
|
markdown_cell(module["problem_intro"]),
|
|
markdown_cell(f"## How To Use This Notebook\n\n{module['problem_how']}"),
|
|
code_cell(SETUP),
|
|
code_cell(COMMON_IMPORTS),
|
|
]
|
|
|
|
for heading, questions in module["problem_sets"]:
|
|
cells.append(
|
|
markdown_cell(
|
|
f"## {heading}\n\nTreat this block as a miniature review situation. Choose the answer that would best survive an engineering conversation."
|
|
)
|
|
)
|
|
cells.append(quiz_code(questions, heading))
|
|
|
|
cells.extend(
|
|
[
|
|
markdown_cell(f"## Mini Case\n\n{module['problem_case']}"),
|
|
markdown_cell(
|
|
"""
|
|
## What These Questions Are Really Testing
|
|
|
|
The multiple-choice format is only the surface. Underneath it, the notebook is testing whether your explanation can survive small shifts in phrasing, emphasis, and review context. If your understanding is robust, the wording can change and the same mechanism will still come into focus. If your understanding is fragile, the wording change will tempt you back into vague or prestige-based answers. That is why these problems matter.
|
|
"""
|
|
),
|
|
markdown_cell(
|
|
"""
|
|
## Common Failure Mode
|
|
|
|
A common failure mode at this stage is to answer with something broadly true but locally weak. For example, saying that a circuit uses superposition or interference may be accurate, yet still fail to identify what role a specific block is playing in the present design. The goal of the problems notebook is to eliminate that kind of vague correctness and replace it with circuit-specific explanation.
|
|
"""
|
|
),
|
|
markdown_cell("## Written Checks\n\nUse the prompts below to rehearse full-sentence engineering explanations."),
|
|
reflection_code(module["problem_reflections"][0]),
|
|
reflection_code(module["problem_reflections"][1]),
|
|
markdown_cell(f"## Exit Condition\n\n{module['problem_exit']}"),
|
|
]
|
|
)
|
|
return notebook(cells)
|
|
|
|
|
|
def build_studio(module: dict) -> dict:
|
|
cells = [
|
|
markdown_cell(f"# {module['lecture_title']} Studio"),
|
|
markdown_cell(module["studio_intro"]),
|
|
markdown_cell(f"## Design Brief\n\n{module['studio_brief']}"),
|
|
code_cell(SETUP),
|
|
code_cell(COMMON_IMPORTS),
|
|
]
|
|
|
|
for section in module["studio_sections"]:
|
|
cells.append(markdown_cell(f"## {section['heading']}\n\n{section['intro']}"))
|
|
cells.append(
|
|
editable_lab_code(
|
|
section["editable_code"],
|
|
title=section["editable_title"],
|
|
instructions=section["editable_instructions"],
|
|
)
|
|
)
|
|
|
|
cells.extend(
|
|
[
|
|
quiz_code(module["studio_quiz"], "Studio Design Check"),
|
|
markdown_cell(f"## Studio Debrief\n\n{module['studio_debrief']}"),
|
|
markdown_cell(
|
|
"""
|
|
## Studio Standard
|
|
|
|
A strong studio notebook is compact, explicit, and reviewable. It does not hide behind volume. It makes clear what family of circuits was explored, what invariant mechanism survived across the variants, what evidence justified the final recommendation, and what tradeoffs remained open. If your notebook cannot answer those questions yet, keep refining it. That refinement is the studio.
|
|
"""
|
|
),
|
|
markdown_cell(
|
|
"""
|
|
## What A Finished Studio Should Feel Like
|
|
|
|
The finished notebook should feel like a small engineering artifact rather than a scrapbook. A reviewer should be able to open it, understand the task, inspect the candidate circuits, see the evidence, and understand why one recommendation won. If that standard is met on these small modules, later capstone work becomes much more realistic.
|
|
"""
|
|
),
|
|
reflection_code(module["studio_reflections"][0]),
|
|
reflection_code(module["studio_reflections"][1]),
|
|
reflection_code(module["studio_reflections"][2]),
|
|
reflection_code(module["studio_reflections"][3]),
|
|
]
|
|
)
|
|
return notebook(cells)
|
|
|
|
|
|
def main() -> None:
|
|
outputs: dict[Path, dict] = {}
|
|
for module in MODULES:
|
|
outputs[module["dir"] / "lecture.ipynb"] = build_lecture(module)
|
|
outputs[module["dir"] / "lab.ipynb"] = build_lab(module)
|
|
outputs[module["dir"] / "problems.ipynb"] = build_problems(module)
|
|
outputs[module["dir"] / "studio.ipynb"] = build_studio(module)
|
|
|
|
for path, payload in outputs.items():
|
|
write_notebook(path, payload)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|