Skip to content

Graph-theory course: fill the algorithm gaps - #29

Open
thinkinginmath wants to merge 17 commits into
mainfrom
codex/graph-course-algorithms
Open

Graph-theory course: fill the algorithm gaps#29
thinkinginmath wants to merge 17 commits into
mainfrom
codex/graph-course-algorithms

Conversation

@thinkinginmath

Copy link
Copy Markdown
Contributor

What this PR is

A working branch for the algorithms a 34-module graph-theory course asks for and 0.7.0 does not draw. The first commit is the work list: docs/graph-course-gaps.md. Each subsequent commit on this branch should close one numbered item from that list.

How to work this branch

  • Read docs/graph-course-gaps.md first. It has the 14 items, ordered by how many course modules each unblocks, and for each: what the figure shows, the input surface, what to refuse and with which witness, and the course instance to test on. The course's own TEMPLATE GAP: lines (in ../graph-theory/modules/*/topic.md) carry the exact JSON the authors wanted to send.
  • Follow the Add checked graph connectivity and CS structure visuals #28 pattern (written down in the doc's first section): <name>_steps in straightedge/graphs.pyALGORITHMS + routing in templates/graph_algorithm.pyEXAMPLES → legibility CORPUS entry → tests for the drawing, the refusal, and the reveal timing → docs + CHANGELOG bullet. Optional Manim concept only where the course wants a video.
  • One item per commit, subject Add <template>: <one line>; tick it off in the doc's list in the same commit (or note what was left out and why).
  • Do not scope the "Blocked on structural work" section into an item — multigraphs, the Manim vertex cap, composite panels, N-way partitions, list-valued faces, comparison with figure children. Those are separate PRs.
  • Determinism matters: captions and panels must not iterate sets (tests/test_determinism.py runs under two PYTHONHASHSEEDs). If you add a role to templates/graph.py's stylesheet, run python tools/build_site_figures.py and commit the regenerated site SVGs, or --check fails CI.

Verification per item

python -m pytest -q                      # incl. determinism + legibility
python tools/build_site_figures.py --check
git diff --check

Status

  • Work list (docs/graph-course-gaps.md, README link)
  • 1 prufer_encode / prufer_decode (08)
  • 2 havel_hakimi (05)
  • 3 tree_center (07)
  • 4 ear_decomposition (15) — needs color-N edge roles
  • 5 stable_matching (12)
  • 6 hamiltonian_search (23, 24)
  • 7 turan(n, r) (24) — needs N-way partitions
  • 8 floyd_warshall (i07)
  • 9 mycielski (21)
  • 10 edge_coloring (22) — needs color-N edge roles
  • 11 degeneracy_ordering (20)
  • 12 topological_sort tie_break (i03)
  • 13 scc names Kosaraju + finish-order panel (i09)
  • 14 bipartite_matching Hall violator on the failed search (10, 11, i10)

🤖 Generated with Claude Code

thinkinginmath and others added 17 commits August 26, 2026 19:06
Fourteen scoped items — Prüfer, Havel–Hakimi, tree centre, ear
decomposition, stable matching, Hamiltonian search, Turán, Floyd–Warshall,
Mycielski, edge colouring, degeneracy ordering, topological tie-break, SCC
finish order, the Hall violator on a failed matching — each with the course
modules it unblocks, inputs, what to refuse and with which witness, and the
instance to test on. The #28 pattern for adding one is written down, and the
structural gaps that are not algorithms are fenced off so nobody scopes them
into an item by accident.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Without start_cycle the first ear came from _find_cycle, the directed
finder topological_sort uses for its witness. On an undirected graph it
returns the back-edge to the DFS parent as a two-vertex "cycle", so every
2-connected input — C4, K4, the course's prism, Petersen — was refused with
"the graph has no cycle". Both existing tests passed start_cycle and never
took the default path.

_find_undirected_cycle walks depth-first remembering the arriving edge; a
neighbour already on the stack that is not that edge's other end closes the
cycle. The test covers the prism (all nine edges end up in ears), C4 and K4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thinkinginmath

Copy link
Copy Markdown
Contributor Author

Review of the algorithm work at 54c2485

Reviewed by executing the branch (7 finder angles, 18 candidates verified individually), plus every course instance from docs/graph-course-gaps.md run through its new template. Full suite: 2172 passed; Manim graph smoke renders 8/8; build_site_figures.py --check ok. The ear_decomposition default-path bug (refused every 2-connected graph) is already fixed in 54c2485.

18 of 19 course instances draw or refuse exactly as the work list specifies. Ten findings remain, ranked; each was reproduced on the branch unless marked otherwise.

Blocking

# Where Problem Evidence
1 graphs.py:1051 edge_coloring_steps Unbounded exhaustive search for a Δ-edge-colouring before falling back to Δ+1 — exponential on class-2 graphs inside the 11-vertex cap. render() runs it twice (refusal + frames). edge_coloring on K₉ (accepted by the cap): killed at a 30 s timeout in refusal_findings alone; ~105 s wall measured separately; K₁₁ effectively never returns. One figure request pins a CPU.
2 graphs.py:784 hamiltonian_search_steps Appends a list(path) copy to events for every explored state; only the first max_frames−2 (≤22) are consumed, and nothing bounds the search. K₁₀ + one pendant vertex (11 v): 986,411 states, ~2 M event tuples, 10.6 s, 719 MB RSS for 20 rendered steps — twice per render. Fix: stop appending once len(events) >= max_frames-2; only count after that.
3 graphs.py:1583 (scc), :1770 (matching) New unconditional trailing steps ("Condensation DAG"; "Hall violator" whenever the matching isn't left-perfect) without adjusting MAX_STORYBOARD_STEPS=12 — previously-working storyboards at the cap are now refused, and vertex_cover inherits an unrelated Hall panel. scc, 11-vertex directed path: main renders 12 steps; branch → 13 → refused "at most 12 fit". vertex_cover (A,B,C / x,y; A‑x, B‑x, C‑y): "Hall violator (2 < 3)" now inserted before the cover panel.
4 graphs.py:1473 topological_sort_steps Default tie-break silently changed: old loop rescanned graph.ids (declaration order among ready vertices); new default fifo queues newly-freed vertices at the back; the old policy is no longer reachable (min sorts by _vertex_sort_key). A,B,C with A→B, no tie_break: mainorder: A, B, C; branch → A, C, B. Every 0.7.0-authored storyboard/answer key changes with no param change; docs/graph-animations.md:148 calls it without tie_break; the existing test uses a graph where both orders coincide.

Should fix

# Where Problem Evidence
5 graphs.py:765 expect not in {"cycle", "none"} raises TypeError on an unhashable value; _findings only catches GraphError. hamiltonian_search with expect: ["A","B","C","A"] (natural, since prufer_encode.expect is an array) → TypeError: unhashable type: 'list' from graph_algorithm.py:119 instead of a refusal.
6 templates/havel_hakimi.py:47 Realisation frames pass raw Step roles into highlights.nodes; graph.py has no .graph-node-frontier rule (graph_algorithm._NODE_STATES maps frontier→target for exactly this reason). {'sequence':[3,3,2,2,2],'realize':true} → six graph-node-frontier elements, zero matching CSS rules: the "Join 1 to 2, 3, 4" panel highlights only vertex 1. Route through frames_from_steps.
7 templates/graph_algorithm.py:156 _check_name Finding ids are substring-sniffed from the GraphError message and weren't extended for the new algorithms; _findings picks the walk joiner whenever "cycle" appears. start_cycle='abc'graph_algorithm_cycle ("start_cycle must be an array" contains "cycle"); missing-edge witness (4,0) labelled as walk 4 → 0; "not 2-connected; C is an articulation vertex" → graph_algorithm_nodes; prufer_decode bad entry (position 2, value 9) labelled 2–9 as if an edge. Tests key on these ids (tests/test_graph_algorithm.py:43,104,128,…).
8 templates/floyd_warshall.py:66 Catalogue surface advertises directed default True, but the graph is built via coerce_graph, whose default is False. Omit directed on A→B weight 1 → refused "Floyd–Warshall here needs a directed graph". A caller following the published default gets an empty render.
9 graphs.py:430 havel_hakimi_steps "Decremented" highlights mark the first degree positions of the re-sorted sequence, which aren't necessarily the decremented entries. [3,3,2,2,2] step 1: decrement (3,2,2)→(2,1,1), untouched 2 stays; after sorting [2,2,1,1] with highlights {0,1,2} — an un-decremented 2 is painted, a decremented 1 is not. On the PR's own course example. (traced, not executed)
10 templates/turan.py:16 The 11-vertex cap is checked after turan_graph materialises all O(n²) edges (same after-the-fact pattern in havel_hakimi's MAX_STEPS and prufer_decode's leaf scan). turan(n=2500, r=2) → ~3 s / 1.56 M edges before the refusal; n=200000 would allocate ~10¹⁰ edges first. Check n before building.

Below the cut (real, lower severity)

  • README.md:103 says "49 templates"; the registry has 53 on the branch.
  • graphs.py:1578 condensation label ''.join(component) is unsorted and separator-free (C1: ACB vs panel {A, C, B}; ids 1,10 collide with 11,0).
  • graphs.py:1551 the SCC reverse pass became recursive (reverse_visit) — RecursionError on ~1000-vertex chains where main returned.
  • graph_algorithm.py:253 the edge_labels branch rebuilds from frame_base["edges"], discarding the new visible_edges filter (latent).
  • graph.py:223 two-key partition widening now accepts {'left', 'rigth'} silently; the graph.py / graph_algorithm.py copies disagree on refusal text.
  • Duplication: graph-source dispatch three times in graph_algorithm.py (91/198/219); GraphError→label and animate/storyboard dispatch copied into all four new templates with drifting joiners. Note catalog.py:353-375 AST-scans render() for params.get(...), so a shared helper must leave those reads in place.

Suggested order

1–2 (bound the searches; they're DoS-shaped at the advertised cap) → 3–4 (behaviour changes to main's existing outputs; decide the tie-break default deliberately and document it) → 5–8 → 9–10 → the cut list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant