Skip to content

Commit c1696c8

Browse files
thomasahleclaude
andcommitted
Keep the three polished videos; add the animation SKILL file
- Removed trace_cyclic, delete_the_node, and inverse_split (videos and scenes): superseded by the three that went through full review -- trace_delete, kronecker_trace, softmax_jacobian. - paper/animations/SKILL.md: the distilled playbook from ~40 review rounds -- the book's notation rules for videos, the visual style (semantic color, halos, uniform sizes, framing), the 13-rule animation grammar (valid every frame, glyph identity, count- and direction-matched transforms, Transform-keeps-source, spawn-from- parent, whiskers become edges, circles spent on evaluation, dots carry wires, offset-curve bundles, more-steps-beat-magic), the iterate-at-ql / review-at-1080p workflow, and the tooling pitfalls. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PzG3QbNtaFmABBHBG39wp
1 parent 7cea07a commit c1696c8

6 files changed

Lines changed: 188 additions & 369 deletions

File tree

paper/animations/SKILL.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
---
2+
name: tensor-diagram-videos
3+
description: How to make publication-quality manim videos of tensor-diagram
4+
derivations for the Tensor Cookbook — the animation grammar, the book's
5+
notation rules, the visual style, the review workflow, and the manim
6+
pitfalls, all learned across three videos (trace_delete, kronecker_trace,
7+
softmax_jacobian) and ~40 review rounds.
8+
---
9+
10+
# Making tensor-diagram derivation videos
11+
12+
The product: a 15–40s silent 1080p60 video in which a Matrix-Cookbook-style
13+
identity is *derived*, not asserted. The standard was set by three videos in
14+
this folder: `trace_delete.mp4`, `kronecker_trace.mp4`, `softmax_jacobian.mp4`,
15+
all rendered from scenes in `advanced_rules.py`.
16+
17+
The one-sentence philosophy: **the animation is only a proof if every frame
18+
is a well-formed diagram and every mobject has an ancestry.** Nothing may
19+
appear, vanish, flip, or morph without a semantic reason.
20+
21+
## The book's notation (non-negotiable)
22+
23+
Check `paper/chapters/functions.tex` before inventing anything. Known rules:
24+
25+
- **Function application**: the function is a plain text node (no circle);
26+
the *input* edge carries an arrow pointing INTO the function. Solid arrow
27+
for vector→vector functions (softmax), **densely dotted** for elementwise
28+
ones (exp, pow_k) — and for elementwise functions the data wire stays on
29+
the argument (`exp ←··· z ——`, the free wire belongs to z).
30+
- **Sum** = wire terminated by a copy-dot. **diag(v)** = 3-edged copy-dot
31+
with v hanging off. **1/x** = `pow₋₁` applied by arrow — not a fraction —
32+
though converting pow↔fraction *on screen* is a good beat (go full circle:
33+
open with the fraction, work in pow form, return to fractions at the end).
34+
- No brackets when arrows already disambiguate. Scalar-argument functions
35+
(pow on a sum) still get their application arrow.
36+
- A matrix is a node with two edges; vectors have one (a and b in aᵀXb are
37+
closed by the trace — count edges before drawing!).
38+
- **Covariance**: a named edge exits the same side in every term and every
39+
frame. Two free edges never cross incidentally — after the twist
40+
convention, a crossing MEANS a swap. Rotating a node 180° IS transposition
41+
(so a turned glyph legitimately reads as Aᵀ — relabel by spinning it
42+
upright while the ᵀ fades in).
43+
- Claim only what is derived (the softmax video says "Jacobian of softmax",
44+
not "Hessian of cross-entropy", because the CE connection is never shown).
45+
46+
## Visual style
47+
48+
- White background, `config.frame_height = 5.6` (set in `__init__` before
49+
`super().__init__`), same pixels → everything ~40% bigger. Title top
50+
(`to_edge(UP, buff≈0.4)`), gray caption bottom (`Tex … scale(0.62)`,
51+
`to_edge(DOWN, buff=0.18)`), one clause per beat — the captions ARE the
52+
voiceover script.
53+
- Semantic color, constant per object across formula AND diagram:
54+
variables `#C03B2B` (red), function names `#1F6FB2` (blue), recognized
55+
results `#188A54` (green), derivative apparatus `#B07000` (amber),
56+
structural wires/dots/signs black. The amber thread is pedagogy: whisker →
57+
dangling stub → final edge are visibly the same object.
58+
- **Uniform glyph sizes.** No shrunken denominators or mini-copies; retune
59+
layout instead.
60+
- Glyph halos (`set_background_stroke(color=WHITE, width=6)`), never
61+
`BackgroundRectangle` masks — rectangles clip *other* wires passing behind
62+
(visible the moment the scene moves). A curve's clearance around its own
63+
riding label must be a real parameter gap in the curve, sized from the
64+
local radius.
65+
- Every node gets clearance — edges stop short of glyph ink, including
66+
operator glyphs like flatten triangles (stubs may start *under* a
67+
white-filled node so the fill covers the join).
68+
- Arrows must clear both endpoints: tips short of subscripts (`pow₋₂`),
69+
tails outside the source glyph. When in doubt, widen the gap between the
70+
nodes.
71+
- Finish: `Indicate` when a result lands, `Circumscribe` the final formula,
72+
settle the finished diagram up under its equation, hold ≥2.5s.
73+
74+
## The animation grammar
75+
76+
1. **Every frame is a valid diagram.** Right edge count per tensor, no
77+
transient crossings, invariants hold mid-animation, not just at
78+
endpoints (e.g. delayed growth so dangling edges never cross).
79+
2. **Glyphs keep identity.** A symbol persists (and may glide, with
80+
`path_arc` to hop over others), appears, or disappears — it never smears
81+
into a different symbol. The one sanctioned morph is a semantic renaming
82+
(exp absorbing its normalizer to *become* softmax). Titles: build the
83+
formula in sync with the diagram (X → AXB → Tr(AXB) → ∂·/∂z); keep it
84+
centered early and slide left as `= …` grows; hand-assemble fractions so
85+
the ∂ exists as its own animatable glyph; early title stages sit at the
86+
equation midline, not the numerator slot.
87+
3. **Count-match every transform.** One wire → one wire. If topology
88+
changes, split the target at the join (de Casteljau at a bezier apex;
89+
closed loops split into quadrants) so correspondence is 1:1. A mismatch
90+
makes manim silently duplicate → ghost arcs.
91+
4. **Direction-match: anchored end first.** Transforms map start→start. A
92+
target curve parameterized from its free end makes the source pivot/flip
93+
about the wrong end (this bug shipped four times: trace closure, A–X
94+
edge, j1f, jw2). Build every replacement wire starting from the end that
95+
stays attached.
96+
5. **`Transform` keeps the SOURCE mobject alive** (wearing the target's
97+
shape); `ReplacementTransform` swaps the target in. Corollaries: never
98+
FadeOut/group/animate the *target* of a plain Transform (ghost loops,
99+
stray minus signs, unmoved edges — this bug shipped five times); after a
100+
Transform, later code must reference the source name.
101+
6. **`Transform` preserves z-order; `ReplacementTransform` re-layers to
102+
top.** Use Transform when a morphing wire must stay under labels.
103+
7. **Copies spawn from their parent.** Product rule, chain rule: `.copy()`
104+
superimposed on the original, then transform the copies out to the new
105+
rows/groups. Factor swaps arc over each other (`animate(path_arc=…)`).
106+
8. **The whisker is the promised edge.** One whisker per derivative loop
107+
(per new edge); it survives distribution (loop→loop, dot→dot,
108+
whisker→whisker at hand-offs) and finally *becomes* the new edge. New
109+
edges are born already pointing in their final direction and never
110+
reverse (mirror sub-layouts if needed to keep all j's on one side).
111+
9. **Amber circle = pending derivative; evaluation spends it.** Circling a
112+
function is applying ∂; when you evaluate (exp′=exp, pow₋₁′=−pow₋₂) the
113+
circle fades — and the relabel is the semantic event (the minus is BORN
114+
at pow₋₁′=−pow₋₂: keep ONE sign object centered between terms and flip
115+
it + → − at that moment). A chain-rule link through a scalar carries no
116+
d-edge at all.
117+
10. **Dots terminate their edges.** A sliding sum-dot drags its wire
118+
(shorten in step). Dot-absorption is articulated: slide → merge →
119+
collapse, with the index-level receipt (Σₖδⱼₖ…) as the caption.
120+
11. **Static↔tracker hand-offs must be seamless both ways**: animate the
121+
outgoing static to exactly the incoming `always_redraw`'s initial
122+
state (labels glide with relaxing wires; freeze redraws to statics
123+
before a group shift).
124+
12. **Bundles are offset curves of ONE center path** (foot-curl → dome →
125+
foot-curl), strands at ±offset along numeric normals — guaranteed
126+
parallel, and the top strand takes the *inside* of every turn. Draw the
127+
pair at zero gap until the bundling operator is resolved; the gap
128+
opening IS the resolution. Deforming closed curves = one parametric
129+
curve with keyframed shape parameters (never piecewise arcs), labels
130+
riding a fixed parameter point through a real gap.
131+
13. **More steps beat magic.** Rewrite big jumps as chains of small ones,
132+
each with a caption: quotient → product rule with explicit factors;
133+
pow↔fraction conversions; collect terms onto one line (swapping factors
134+
so covariance holds) *before* the final recognition; every `=` in the
135+
classical derivation gets a diagram move, and the object mapping in
136+
each move is reasoned (z's persist, arrows persist, machinery is
137+
absorbed into the name it justifies, split wires turn rather than
138+
respawn).
139+
140+
## Workflow
141+
142+
1. Read the relevant chapter first; the book may already contain the exact
143+
decomposition/figure (softmax) or pose the identity as an exercise.
144+
2. Storyboard the beats; the user may supply frame-by-frame sketches —
145+
follow them panel by panel.
146+
3. Build the scene in `advanced_rules.py` (helpers there: `glyph` halo
147+
text, `farrow` dotted/solid application arrows, `wire`, `cdot`,
148+
`dcircle`, `dloop` (whisker options), `node`, caption closure,
149+
`smgroup`-style builders). Fixed coordinate slots per beat; builders
150+
return dicts of named parts so transforms can be per-piece.
151+
4. Iterate at `-ql`, extracting frames at beat boundaries:
152+
`ffmpeg -ss T -i video.mp4 -frames:v 1 out.png` (get duration via
153+
`ffprobe`), and Read them. Check *mid-transition* frames, not just
154+
settled ones.
155+
5. **Final review at 1080p (`-qh`) — delivery resolution.** 480p
156+
anti-aliasing hides exactly the shippable-defect class: 1–2px gaps,
157+
wires grazing glyphs, mask edges. Use PIL crops for pixel-level checks
158+
of junctions.
159+
6. Ship: copy .py + .mp4 to `paper/animations/`, commit with explicit
160+
pathspec, push. One commit per review round.
161+
7. Expect ~5–15 user review rounds per video. Each note usually
162+
generalizes: fix the instance, then apply the principle everywhere
163+
(e.g. "circle is spent" came from one pow note; uniform font sizes from
164+
one screenshot).
165+
166+
## Tooling pitfalls
167+
168+
- Patch scripts: `assert old in s` for EVERY replacement; a failed assert
169+
before `write` means the file is untouched and the subsequent render
170+
shows the OLD video — check which. Region-scope all `s.index` anchors to
171+
the class (`base = s.index("class Foo"); s.index(anchor, base)`) — a
172+
global `index` can match an earlier scene and splice duplicated chunks
173+
into the file.
174+
- Multiple scenes share `advanced_rules.py`; anchor text ("# ---- title
175+
----", "self.cap = None") repeats across scenes.
176+
- The scratchpad can be wiped between sessions; the repo copy is the
177+
source of truth — restore, then re-apply.
178+
- manim: `MathTex(a, b, c)` part-splitting enables per-glyph color/animate;
179+
`set_background_stroke` for halos; `FadeOut(m, target_position=…)` fades
180+
while translating (absorption); `FadeIn(m, target_position=…)` arrives
181+
from there; `Rotate(m, about_point=…)` for in-place glyph spins;
182+
`DashedLine(...).add_tip(...)` for dotted arrows; `always_redraw`
183+
closures over `ValueTracker`s for deforming geometry (keyframe param
184+
arrays + `np.interp`); `Date`-like nondeterminism absent but LaTeX
185+
compile cache makes re-renders fast.
186+
- Render one scene: `python -m manim render -qh --disable_caching
187+
advanced_rules.py SceneName` (venv: `.venv/bin/python`; latexmk not
188+
needed — manim compiles its own tex).

0 commit comments

Comments
 (0)