mirror of
https://github.com/saymrwulf/QuantumLearning.git
synced 2026-05-14 20:58:00 +00:00
274 lines
10 KiB
Python
274 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
|
|
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", []):
|
|
if cell.get("cell_type") != "markdown":
|
|
continue
|
|
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(
|
|
"## Optional Deep-Dive Reference\n\n"
|
|
"This notebook is not part of the mandatory first-pass mainline. "
|
|
"Use it when you want the full backward-designed mastery map in more depth. "
|
|
"For the mainline flow, return to `../COURSE_BLUEPRINT.ipynb` and follow the next-notebook handoff there."
|
|
)
|
|
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. Use `notebooks/reference/PROFESSIONAL_PATH.ipynb` if you want the long-range mastery map again.\n"
|
|
"3. Extend the capstone with your own circuit family and defend the design choices in writing.\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)
|
|
|
|
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)
|
|
|
|
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."
|
|
)
|
|
bottom_text = (
|
|
f"{NAV_BOTTOM_MARKER}\n"
|
|
"## What To Open Next\n\n"
|
|
f"{next_line}\n\n"
|
|
"If this notebook still feels unstable, repeat it before you move on. "
|
|
"The mainline only works if each handoff is earned."
|
|
)
|
|
|
|
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(),
|
|
[
|
|
(
|
|
"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`. If you want the deeper mastery map later, use `reference/PROFESSIONAL_PATH.ipynb` as an optional side reference.",
|
|
),
|
|
(
|
|
"Open `PROFESSIONAL_PATH.ipynb` next. Then begin the technical sequence.",
|
|
"Open `COURSE_BLUEPRINT.ipynb` next. Then follow the mainline handoff from notebook to notebook. `reference/PROFESSIONAL_PATH.ipynb` is optional and not required for the first pass.",
|
|
),
|
|
],
|
|
)
|
|
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`.",
|
|
),
|
|
(
|
|
"5. Continue with `qiskit_engineering`, then `algorithms`, then `professional` using the same bundle rhythm",
|
|
"5. Continue with `qiskit_engineering`, then `algorithms`, then `professional` using the same bundle rhythm\n6. Use `reference/PROFESSIONAL_PATH.ipynb` only if you want the optional deep mastery map",
|
|
),
|
|
],
|
|
)
|
|
insert_reference_note(reference_notebook_path())
|
|
ensure_navigation()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|