QuantumLearning/scripts/harden_course_flow.py

309 lines
13 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import hashlib
import json
from pathlib import Path
import shutil
from quantum_learning import (
canonical_course_steps,
completion_notebook_path,
entry_notebook_path,
legacy_archive_dir,
project_root,
reference_notebook_path,
)
from quantum_learning.course_flow import LEGACY_SINGLE_NOTEBOOKS
ROOT = project_root()
NOTEBOOKS = ROOT / "notebooks"
REFERENCE_DIR = reference_notebook_path().parent
LEGACY_DIR = legacy_archive_dir()
NAV_TOP_MARKER = "<!-- COURSE_NAV_TOP -->"
NAV_BOTTOM_MARKER = "<!-- COURSE_NAV_BOTTOM -->"
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 markdown_cell(text: str) -> dict:
return {
"cell_type": "markdown",
"metadata": {},
"source": [line if line.endswith("\n") else f"{line}\n" for line in text.splitlines()],
}
def write_notebook(path: Path, cells: list[dict]) -> None:
for index, cell in enumerate(cells):
cell["id"] = cell_id(index, cell)
data = {
"cells": cells,
"metadata": {
"kernelspec": {
"display_name": "QuantumLearning (.venv)",
"language": "python",
"name": "python3",
},
"language_info": {
"name": "python",
"version": "3.12",
},
},
"nbformat": 4,
"nbformat_minor": 5,
}
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n")
def load_notebook(path: Path) -> dict:
return json.loads(path.read_text())
def save_notebook(path: Path, notebook: dict) -> None:
for index, cell in enumerate(notebook.get("cells", [])):
cell["id"] = cell_id(index, cell)
path.write_text(json.dumps(notebook, indent=2, ensure_ascii=False) + "\n")
def relative_link(source: Path, target: Path) -> str:
return str(target.relative_to(source.parent, walk_up=True))
def replace_text(path: Path, replacements: list[tuple[str, str]]) -> None:
notebook = load_notebook(path)
changed = False
for cell in notebook.get("cells", []):
text = "".join(cell.get("source", []))
original = text
for old, new in replacements:
text = text.replace(old, new)
if text != original:
cell["source"] = [line if line.endswith("\n") else f"{line}\n" for line in text.splitlines()]
changed = True
if changed:
save_notebook(path, notebook)
def insert_reference_note(path: Path) -> None:
notebook = load_notebook(path)
note = markdown_cell(
"## Hidden Reference Notebook\n\n"
"This notebook is intentionally hidden from the visible Jupyter root. "
"It exists as an internal deep-reference document, not as part of the supported consumer route. "
"The supported learner path is `../START_HERE.ipynb -> ../COURSE_BLUEPRINT.ipynb -> mainline bundles -> ../COURSE_COMPLETE.ipynb`."
)
if any(NAV_TOP_MARKER in "".join(cell.get("source", [])) for cell in notebook["cells"]):
pass
existing = "".join(notebook["cells"][1].get("source", [])) if len(notebook["cells"]) > 1 else ""
if "Optional Deep-Dive Reference" not in existing:
notebook["cells"].insert(1, note)
save_notebook(path, notebook)
def build_course_complete() -> None:
path = completion_notebook_path()
cells = [
markdown_cell(
"# Course Complete\n\n"
"You have reached the end of the enforced mainline course path. "
"That does not mean circuit design is 'done.' It means you now have a coherent end-to-end foundation: "
"principles, engineering workflow, algorithmic patterns, and professional review habits."
),
markdown_cell(
"## What You Should Be Able To Do Now\n\n"
"- build and explain gate-model circuits from first principles\n"
"- move between code, circuit drawings, state intuition, counts, transpiled structure, and noisy behavior\n"
"- compare candidate circuit designs under explicit constraints\n"
"- write review-style justifications instead of vague impressions\n"
"- diagnose whether a mismatch is conceptual, structural, transpilation-driven, or noise-driven"
),
markdown_cell(
"## What To Do Next\n\n"
"1. Revisit the hardest studio notebooks and tighten your written design judgements.\n"
"2. Extend the capstone with your own circuit family and defend the design choices in writing.\n"
"3. Repeat a full module bundle deliberately only when you know exactly which skill you are repairing.\n"
"4. Only after that, add external hardware targets or new research notebooks."
),
markdown_cell(
"## Final Rule\n\n"
"Do not measure progress by how many notebooks you opened. "
"Measure it by whether you can predict, explain, edit, compare, and defend circuit behavior without hand-waving."
),
]
write_notebook(path, cells)
def move_optional_and_legacy_notebooks() -> None:
REFERENCE_DIR.mkdir(parents=True, exist_ok=True)
LEGACY_DIR.mkdir(parents=True, exist_ok=True)
visible_reference_dir = NOTEBOOKS / "reference"
if visible_reference_dir.exists() and not REFERENCE_DIR.exists():
visible_reference_dir.rename(REFERENCE_DIR)
root_professional_path = NOTEBOOKS / "PROFESSIONAL_PATH.ipynb"
if root_professional_path.exists():
target = reference_notebook_path()
target.parent.mkdir(parents=True, exist_ok=True)
root_professional_path.replace(target)
visible_reference_path = visible_reference_dir / "PROFESSIONAL_PATH.ipynb"
if visible_reference_path.exists() and visible_reference_path != reference_notebook_path():
reference_notebook_path().parent.mkdir(parents=True, exist_ok=True)
visible_reference_path.replace(reference_notebook_path())
if visible_reference_dir.exists():
for child in sorted(visible_reference_dir.iterdir()):
target = REFERENCE_DIR / child.name
if target.exists():
continue
shutil.move(str(child), str(target))
remaining = list(visible_reference_dir.iterdir())
if not remaining:
visible_reference_dir.rmdir()
for notebook_name in LEGACY_SINGLE_NOTEBOOKS:
source = NOTEBOOKS / notebook_name
if source.exists():
source.replace(LEGACY_DIR / notebook_name)
def ensure_navigation() -> None:
steps = canonical_course_steps()
total_steps = len(steps)
for index, step in enumerate(steps):
path = ROOT / step.path
notebook = load_notebook(path)
previous_step = steps[index - 1] if index > 0 else None
next_step = steps[index + 1] if index + 1 < total_steps else None
prev_line = (
f"Previous notebook: [{previous_step.title}]({relative_link(path, previous_step.absolute_path)})"
if previous_step is not None
else "Previous notebook: none. This is the start of the mainline course."
)
next_line = (
f"Next notebook: [{next_step.title}]({relative_link(path, next_step.absolute_path)})"
if next_step is not None
else "Next notebook: none. This notebook closes the mainline course."
)
top_text = (
f"{NAV_TOP_MARKER}\n"
"## Mainline Navigation\n\n"
f"Step {index + 1} of {total_steps}. Follow the mainline in order and do not skip ahead.\n\n"
f"{prev_line}\n\n"
f"{next_line}\n\n"
"Rule: finish this notebook top-to-bottom before you open the next one."
)
if next_step is None:
bottom_text = (
f"{NAV_BOTTOM_MARKER}\n"
"## What To Open Next\n\n"
f"{next_line}\n\n"
"This is the end of the guarded mainline route."
)
else:
bottom_text = (
f"{NAV_BOTTOM_MARKER}\n"
"## What To Open Next\n\n"
f"{next_line}\n\n"
"When you finish this notebook, open the next notebook shown above. "
"Stay on the guarded mainline route."
)
top_cell = markdown_cell(top_text)
bottom_cell = markdown_cell(bottom_text)
top_index = next(
(
cell_index
for cell_index, cell in enumerate(notebook["cells"])
if cell.get("cell_type") == "markdown"
and NAV_TOP_MARKER in "".join(cell.get("source", []))
),
None,
)
if top_index is None:
notebook["cells"].insert(1 if notebook["cells"] else 0, top_cell)
else:
notebook["cells"][top_index] = top_cell
bottom_index = next(
(
cell_index
for cell_index, cell in enumerate(notebook["cells"])
if cell.get("cell_type") == "markdown"
and NAV_BOTTOM_MARKER in "".join(cell.get("source", []))
),
None,
)
if bottom_index is None:
notebook["cells"].append(bottom_cell)
else:
notebook["cells"][bottom_index] = bottom_cell
save_notebook(path, notebook)
def main() -> None:
move_optional_and_legacy_notebooks()
build_course_complete()
replace_text(
entry_notebook_path(),
[
(
"Read the rest of this notebook, then open `PROFESSIONAL_PATH.ipynb`. That notebook explains the backward-designed apprenticeship model behind the entire course. After that, return and begin the technical sequence at `00_circuit_literacy.ipynb`.\n\nThe reason for this order is simple. If you do not know the target profession, the beginner material feels either trivial or arbitrary. Once you know the target profession, the same material reads as deliberate foundation-building.",
"Read the rest of this notebook, then open `COURSE_BLUEPRINT.ipynb`. That is the first serious orientation notebook in the enforced mainline path. After that, follow the notebook-to-notebook handoff from inside the notebooks themselves instead of guessing from the filesystem.",
),
(
"After this notebook, open `PROFESSIONAL_PATH.ipynb`. Then move into `00_circuit_literacy.ipynb`.",
"After this notebook, open `COURSE_BLUEPRINT.ipynb`. Then begin `foundations/module_01_principles_and_circuit_literacy/lecture.ipynb`.",
),
(
"Open `PROFESSIONAL_PATH.ipynb` next. Then begin the technical sequence.",
"Open `COURSE_BLUEPRINT.ipynb` next. Then follow the mainline handoff from notebook to notebook. Do not branch away from that route on the first run.",
),
(
"'What should you open immediately after this notebook?', 'options': ['The capstone review notebook', 'The README again', 'PROFESSIONAL_PATH.ipynb'], 'correct_index': 2, 'explanation': 'The professional path notebook gives the didactical context for the whole curriculum.'",
"'What should you open immediately after this notebook?', 'options': ['The capstone review notebook', 'The README again', 'COURSE_BLUEPRINT.ipynb'], 'correct_index': 2, 'explanation': 'Course Blueprint is the next required notebook in the guarded mainline path.'",
),
],
)
replace_text(
entry_notebook_path(),
[
(
"# Start Here\n\nThis notebook is the front door of the course.",
"# Start Here\n\nThis notebook is the only supported starting point for the course. Do not guess where to begin from the filesystem. Start here, run the cells in order, and follow the next-notebook handoff at the end.",
)
],
)
replace_text(
NOTEBOOKS / "COURSE_BLUEPRINT.ipynb",
[
(
"This notebook is the new front door of the platform.",
"This notebook is the first serious orientation notebook after `START_HERE.ipynb`.",
),
(
"7. Only then return to the later single-notebook materials as transition content",
"7. Continue following the explicit next-notebook handoff until `COURSE_COMPLETE.ipynb`",
),
],
)
insert_reference_note(reference_notebook_path())
ensure_navigation()
if __name__ == "__main__":
main()