Graph-theory course: fill the algorithm gaps - #29
Open
thinkinginmath wants to merge 17 commits into
Open
Conversation
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>
Contributor
Author
Review of the algorithm work at
|
| # | 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: main → order: 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:103says "49 templates"; the registry has 53 on the branch.graphs.py:1578condensation label''.join(component)is unsorted and separator-free (C1: ACBvs panel{A, C, B}; ids1,10collide with11,0).graphs.py:1551the SCC reverse pass became recursive (reverse_visit) —RecursionErroron ~1000-vertex chains wheremainreturned.graph_algorithm.py:253theedge_labelsbranch rebuilds fromframe_base["edges"], discarding the newvisible_edgesfilter (latent).graph.py:223two-key partition widening now accepts{'left', 'rigth'}silently; thegraph.py/graph_algorithm.pycopies disagree on refusal text.- Duplication: graph-source dispatch three times in
graph_algorithm.py(91/198/219);GraphError→labeland animate/storyboard dispatch copied into all four new templates with drifting joiners. Notecatalog.py:353-375AST-scansrender()forparams.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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
docs/graph-course-gaps.mdfirst. 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 ownTEMPLATE GAP:lines (in../graph-theory/modules/*/topic.md) carry the exact JSON the authors wanted to send.<name>_stepsinstraightedge/graphs.py→ALGORITHMS+ routing intemplates/graph_algorithm.py→EXAMPLES→ legibilityCORPUSentry → tests for the drawing, the refusal, and the reveal timing → docs + CHANGELOG bullet. Optional Manim concept only where the course wants a video.Add <template>: <one line>; tick it off in the doc's list in the same commit (or note what was left out and why).faces,comparisonwith figure children. Those are separate PRs.tests/test_determinism.pyruns under twoPYTHONHASHSEEDs). If you add a role totemplates/graph.py's stylesheet, runpython tools/build_site_figures.pyand commit the regenerated site SVGs, or--checkfails CI.Verification per item
Status
docs/graph-course-gaps.md, README link)prufer_encode/prufer_decode(08)havel_hakimi(05)tree_center(07)ear_decomposition(15) — needscolor-Nedge rolesstable_matching(12)hamiltonian_search(23, 24)turan(n, r)(24) — needs N-waypartitionsfloyd_warshall(i07)mycielski(21)edge_coloring(22) — needscolor-Nedge rolesdegeneracy_ordering(20)topological_sorttie_break(i03)sccnames Kosaraju + finish-order panel (i09)bipartite_matchingHall violator on the failed search (10, 11, i10)🤖 Generated with Claude Code