Skip to content

Commit cfb6a99

Browse files
committed
added files
1 parent 0bfb161 commit cfb6a99

17 files changed

Lines changed: 11908 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
_build/
2+
.ipynb_checkpoints/
3+
_config_execute.yml
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Notebooks and the Jupyter Book of the QML book
2+
3+
## The notebooks
4+
5+
One notebook per chapter, `chapterN/chapterN.ipynb`, **generated** from `../chapterN.tex` and `../BookPrograms/chapterN/` by
6+
7+
cd doc/Book/BookQML
8+
python3 BookPrograms/tex_to_notebook.py Notebooks # all chapters
9+
python3 BookPrograms/tex_to_notebook.py Notebooks 2 # one chapter
10+
11+
and executed with
12+
13+
cd Notebooks/chapter2 && jupyter nbconvert --to notebook --execute --inplace chapter2.ipynb
14+
15+
Never edit the notebooks by hand: change the `.tex` or the programs and regenerate. TikZ figures of a chapter are rendered to `../BookFigures/chapterN/<label>.png` by `BookPrograms/render_tikz.py` (needs `pdflatex` with the `standalone` class and `pdftoppm`). The generator needs only the Python standard library; executing the notebooks needs `numpy`, `matplotlib`, `jupyter`/`nbconvert`. Chapter 2 takes about 80 s to execute (the kernel-alignment program is the slowest cell), chapter 3 about 75 s (`maxcut.py` and `portfolio.py`), chapter 4 about 4 min (`barren.py`, `timedependent.py` and `regression.py` are the slowest cells), chapter 5 about 3 min (`classical_data.py` is the slowest cell); chapters 6 and 7 have no code beyond the bootstrap cell.
16+
17+
## The Jupyter Book
18+
19+
This folder is also the source of a [Jupyter Book](https://jupyterbook.org) (version 1.x) made of the chapter notebooks:
20+
21+
cd doc/Book/BookQML/Notebooks
22+
pip install -r requirements.txt # once; pins jupyter-book < 2
23+
python3 build_book.py # writes _build/html/index.html
24+
python3 build_book.py --clean # rebuild from scratch
25+
python3 build_book.py --check # bibliography + consistency checks only
26+
python3 build_book.py --regenerate # regenerate the notebooks of _toc.yml from the LaTeX first
27+
python3 build_book.py --execute # re-execute every notebook during the build
28+
29+
| File | Role |
30+
|---|---|
31+
| `_config.yml` | Title, author, repository, MathJax and bibliography settings. `execute_notebooks: "off"`: the notebooks are committed with their outputs and the book shows those |
32+
| `_toc.yml` | Table of contents. Chapter 5 is commented out until its notebook exists: uncomment the line when `chapterN/chapterN.ipynb` has been generated (and update the row in `intro.md`) |
33+
| `intro.md` | Landing page: what the book is, chapter table, how to run the code |
34+
| `references.md`, `references.bib` | Reference list. `references.bib` is **generated** by `build_book.py` from `../chapter*.bib`; edit the chapter files |
35+
| `_ext/bibkeys.py` | Small Sphinx extension: the reference list is labelled by BibTeX key, because the notebooks print citations as `[Key2021]` |
36+
| `build_book.py` | Merges the bibliographies, checks that every `_toc.yml` file and every figure exists, then calls `jupyter-book build .` |
37+
| `requirements.txt` | Packages for building and for executing |
38+
| `.gitignore` | Keeps `_build/` out of the repository |
39+
40+
Everything here is a copy of the corresponding file of `doc/Book/Notebooks` (the quantum-computing book) with the paths and titles of this book; keep the two in step when one of them is improved.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Jupyter Book (version 1.x) configuration for "Quantum Machine Learning".
2+
# Build with python3 build_book.py (or jupyter-book build . from this folder).
3+
title: Quantum Machine Learning
4+
author: Morten Hjorth-Jensen
5+
copyright: "2026"
6+
only_build_toc_files: true # README.md and anything not in _toc.yml are ignored
7+
exclude_patterns: [_build, "**.ipynb_checkpoints", README.md, "_ext/*"]
8+
9+
# The notebooks are generated from the LaTeX and committed *with* their outputs
10+
# (see README.md), so the book does not re-execute them. Use "force" (or
11+
# python3 build_book.py --execute) to re-run everything; a few minutes.
12+
execute:
13+
execute_notebooks: "off"
14+
timeout: 900
15+
allow_errors: false
16+
17+
parse:
18+
myst_enable_extensions: [amsmath, dollarmath, linkify, smartquotes, substitution]
19+
20+
bibtex_bibfiles:
21+
- references.bib # merged from ../chapter*.bib by build_book.py
22+
23+
latex:
24+
latex_documents:
25+
targetname: qmlbook.tex
26+
27+
html:
28+
use_repository_button: true
29+
use_issues_button: true
30+
use_edit_page_button: false # the notebooks are generated, never edited
31+
use_multitoc_numbering: true
32+
home_page_in_navbar: true
33+
comments:
34+
hypothesis: false
35+
36+
repository:
37+
url: https://github.com/QuantumComputingQuantumMachineLearning/QuantumComputing
38+
path_to_book: doc/Book/BookQML/Notebooks
39+
branch: main
40+
41+
# No launch buttons: the notebooks import the programs of BookPrograms/, so they are
42+
# run from a clone of the repository (see intro.md), not from Colab.
43+
44+
sphinx:
45+
local_extensions:
46+
bibkeys: _ext # labels the reference list by BibTeX key, as the text does
47+
config:
48+
bibtex_default_style: keylabels
49+
bibtex_reference_style: label
50+
html_show_copyright: true
51+
suppress_warnings: ["mystnb.unknown_mime_type", "myst.header"]
52+
mathjax3_config:
53+
tex:
54+
tags: none # equation numbers are written by the generator as \tag{N.m}
55+
macros:
56+
bm: ["\\boldsymbol{#1}", 1]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Sphinx extension for the Jupyter Book: a pybtex style that labels every
2+
entry of the reference list by its BibTeX key. The chapter notebooks print
3+
citations as the bracketed key, [Choi2021], so the list on the References
4+
page can be searched for exactly what the text shows."""
5+
from pybtex.plugin import register_plugin
6+
from pybtex.style.formatting.unsrt import Style as UnsrtStyle
7+
from pybtex.style.labels import BaseLabelStyle
8+
from pybtex.style.sorting.author_year_title import SortingStyle
9+
10+
11+
class KeyLabelStyle(BaseLabelStyle):
12+
def format_labels(self, sorted_entries):
13+
for entry in sorted_entries:
14+
yield entry.key
15+
16+
17+
class KeyLabels(UnsrtStyle):
18+
default_label_style = KeyLabelStyle
19+
default_sorting_style = SortingStyle
20+
21+
22+
def setup(app):
23+
register_plugin("pybtex.style.formatting", "keylabels", KeyLabels)
24+
return {"parallel_read_safe": True, "parallel_write_safe": True}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Table of contents of the Jupyter Book. One entry per chapter notebook;
2+
# chapters whose notebook does not exist yet are commented out -- uncomment
3+
# them when Notebooks/chapterN/chapterN.ipynb has been generated.
4+
format: jb-book
5+
root: intro
6+
options:
7+
numbered: false # the notebook titles already read "Chapter N: ..."
8+
parts:
9+
- caption: Foundations
10+
chapters:
11+
- file: chapter1/chapter1
12+
- file: chapter2/chapter2
13+
- caption: Variational models
14+
chapters:
15+
- file: chapter3/chapter3
16+
- file: chapter4/chapter4
17+
- file: chapter5/chapter5
18+
- caption: Outlook
19+
chapters:
20+
- file: chapter6/chapter6
21+
- file: chapter7/chapter7
22+
- caption: Back matter
23+
chapters:
24+
- file: references
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
"""Build the Jupyter Book of "Quantum Machine Learning" (a copy of the
2+
script of the same name in doc/Book/Notebooks, with the paths of BookQML).
3+
4+
cd doc/Book/BookQML/Notebooks
5+
python3 build_book.py # merge the bibliographies, check the table of contents, build the HTML
6+
python3 build_book.py --regenerate # first regenerate every notebook of _toc.yml from the LaTeX
7+
python3 build_book.py --execute # re-execute all notebooks during the build (a few minutes)
8+
python3 build_book.py --clean # remove _build first
9+
python3 build_book.py --check # everything except the build itself (no jupyter-book needed)
10+
11+
The book is the set of generated chapter notebooks chapterN/chapterN.ipynb
12+
(see README.md; never edit them by hand) plus intro.md and references.md.
13+
Three things are prepared before ``jupyter-book build .`` is called:
14+
15+
1. references.bib is merged from ../chapter*.bib. The chapter files repeat
16+
entries that several chapters cite; sphinxcontrib-bibtex refuses duplicate
17+
keys, so the first occurrence is kept and a differing repeat is reported.
18+
2. Every file of _toc.yml must exist, and every chapter notebook that exists
19+
must be in _toc.yml (a commented-out entry is reported, not an error).
20+
3. Every figure a notebook refers to (../../BookFigures/chapterN/*.png) must
21+
exist; run the chapter programs and BookPrograms/render_tikz.py if not.
22+
23+
Only the standard library is needed for the preparation; the build needs the
24+
packages of requirements.txt.
25+
"""
26+
import argparse, json, pathlib, re, shutil, subprocess, sys
27+
28+
HERE = pathlib.Path(__file__).resolve().parent # doc/Book/BookQML/Notebooks
29+
BOOK = HERE.parent # doc/Book/BookQML
30+
31+
32+
def split_bib(text):
33+
"""Yield (key, entry) for every @type{key, ...} of a BibTeX file, by brace counting."""
34+
for m in re.finditer(r"@(\w+)\s*\{\s*([^,\s]+)\s*,", text):
35+
if m.group(1).lower() in ("comment", "string", "preamble"):
36+
continue
37+
depth, i = 0, m.start()
38+
while i < len(text):
39+
if text[i] == "{":
40+
depth += 1
41+
elif text[i] == "}":
42+
depth -= 1
43+
if depth == 0:
44+
break
45+
i += 1
46+
yield m.group(2), text[m.start():i + 1]
47+
48+
49+
def merge_bibliographies():
50+
entries, origin, clashes = {}, {}, []
51+
files = sorted(BOOK.glob("chapter*.bib"), key=lambda p: int(re.sub(r"\D", "", p.stem) or 0))
52+
for bib in files:
53+
for key, entry in split_bib(bib.read_text(encoding="utf-8")):
54+
if key in entries:
55+
if re.sub(r"\s+", "", entry) != re.sub(r"\s+", "", entries[key]):
56+
clashes.append(f"{key}: {bib.name} differs from {origin[key]} (kept {origin[key]})")
57+
continue
58+
entries[key], origin[key] = entry, bib.name
59+
header = ("% Generated by build_book.py from " + ", ".join(b.name for b in files)
60+
+ " -- do not edit; edit the chapter .bib files.\n\n")
61+
(HERE / "references.bib").write_text(header + "\n\n".join(entries[k] for k in sorted(entries, key=str.lower)) + "\n",
62+
encoding="utf-8")
63+
print(f"references.bib: {len(entries)} entries from {len(files)} files")
64+
for c in clashes:
65+
print(" note, duplicate key with different text --", c)
66+
return entries
67+
68+
69+
def toc_files():
70+
text = (HERE / "_toc.yml").read_text(encoding="utf-8")
71+
active = re.findall(r"^\s*-?\s*(?:file|root):\s*(\S+)", text, flags=re.M)
72+
commented = re.findall(r"^\s*#\s*-\s*file:\s*(\S+)", text, flags=re.M)
73+
return active, commented
74+
75+
76+
def resolve(name):
77+
for ext in ("", ".ipynb", ".md"):
78+
p = HERE / (name + ext)
79+
if p.is_file():
80+
return p
81+
return None
82+
83+
84+
def check(entries):
85+
ok = True
86+
active, commented = toc_files()
87+
for name in active:
88+
if resolve(name) is None:
89+
print(f"MISSING: _toc.yml lists {name} but there is no such notebook or page"); ok = False
90+
listed = {str(resolve(n)) for n in active if resolve(n)}
91+
for nb in sorted(HERE.glob("chapter*/chapter*.ipynb")):
92+
rel = nb.relative_to(HERE).with_suffix("").as_posix()
93+
if str(nb) not in listed:
94+
where = "commented out in" if rel in commented else "absent from"
95+
print(f"note: {rel}.ipynb exists but is {where} _toc.yml")
96+
cited = set()
97+
for name in active:
98+
p = resolve(name)
99+
if p is None or p.suffix != ".ipynb":
100+
continue
101+
nb = json.loads(p.read_text(encoding="utf-8"))
102+
md = "\n".join("".join(c["source"]) for c in nb["cells"] if c["cell_type"] == "markdown")
103+
for img in re.findall(r"!\[[^\]]*\]\(([^)\s]+)\)", md):
104+
if not img.startswith("http") and not (p.parent / img).resolve().is_file():
105+
print(f"MISSING figure in {p.name}: {img}"); ok = False
106+
for group in re.findall(r"\[([A-Za-z][\w:.+-]*\d{4}\w*(?:,\s*[A-Za-z][\w:.+-]*\d{4}\w*)*)\]", md):
107+
cited.update(k.strip() for k in group.split(","))
108+
n_code = sum(c["cell_type"] == "code" for c in nb["cells"])
109+
n_out = sum(c["cell_type"] == "code" and bool(c.get("outputs")) for c in nb["cells"])
110+
if n_code > 1 and n_out == 0:
111+
print(f"note: {p.name} has no stored outputs; execute it (README.md) or build with --execute")
112+
unknown = sorted(k for k in cited if k not in entries)
113+
if unknown:
114+
print("note: cited in the notebooks but in no chapter .bib file:", ", ".join(unknown))
115+
return ok
116+
117+
118+
def regenerate():
119+
active, _ = toc_files()
120+
numbers = sorted({int(m.group(1)) for n in active if (m := re.match(r"chapter(\d+)/", n))})
121+
cmd = [sys.executable, str(BOOK / "BookPrograms" / "tex_to_notebook.py"), str(HERE), ",".join(map(str, numbers))]
122+
print(" ".join(cmd)); subprocess.check_call(cmd, cwd=BOOK)
123+
print("The regenerated notebooks have no outputs: execute them (README.md) or build with --execute.")
124+
125+
126+
if __name__ == "__main__":
127+
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
128+
ap.add_argument("--regenerate", action="store_true", help="regenerate the notebooks of _toc.yml from the LaTeX first")
129+
ap.add_argument("--execute", action="store_true", help="re-execute every notebook during the build")
130+
ap.add_argument("--clean", action="store_true", help="remove _build before building")
131+
ap.add_argument("--check", action="store_true", help="prepare and check only, do not build")
132+
args = ap.parse_args()
133+
134+
if args.regenerate:
135+
regenerate()
136+
good = check(merge_bibliographies())
137+
if not good:
138+
sys.exit("Fix the problems above before building.")
139+
if args.check:
140+
sys.exit(0)
141+
if args.clean and (HERE / "_build").exists():
142+
shutil.rmtree(HERE / "_build")
143+
exe = shutil.which("jupyter-book") or shutil.which("jb")
144+
if exe is None:
145+
sys.exit("jupyter-book is not installed: pip install -r requirements.txt")
146+
version = subprocess.run([exe, "--version"], capture_output=True, text=True).stdout
147+
m = re.search(r"Jupyter Book\s*:\s*(\d+)", version) or re.search(r"(\d+)\.\d+", version)
148+
if m and int(m.group(1)) >= 2:
149+
sys.exit("This book is configured for Jupyter Book 1.x (_config.yml, _toc.yml): pip install -r requirements.txt")
150+
cmd = [exe, "build", str(HERE)]
151+
if args.execute:
152+
cmd += ["--config", str(HERE / "_config_execute.yml")]
153+
cfg = (HERE / "_config.yml").read_text(encoding="utf-8").replace('execute_notebooks: "off"', 'execute_notebooks: "force"')
154+
(HERE / "_config_execute.yml").write_text(cfg, encoding="utf-8")
155+
print(" ".join(cmd))
156+
code = subprocess.call(cmd)
157+
if code == 0:
158+
print(f"\nOpen {HERE / '_build' / 'html' / 'index.html'}")
159+
sys.exit(code)

0 commit comments

Comments
 (0)