LR Crash Test
An interactive crash test for the learning-rate stability boundary on L(x) = x². The learner cranks an in-SVG slider from 0.01 to 1.5, taps Step, and watches the dot converge cleanly, oscillate across the minimum, or explode off the right edge of the plot. The walker auto-classifies its own behaviour from the trajectory — observe / safe / edge / explode / insight — and recolours the dot, trail, slider, and narration to match.
Each step is x_new = x − lr × 2x = x(1 − 2lr). The stability boundary is lr = 1/L_max where L_max is the largest eigenvalue of the Hessian (= 2 for L = x²). That's why the danger zone on the slider starts at 1.0 — past it, every step grows instead of shrinks.
The dot starts at x = 5 on the parabola L = x². Each step moves it by −lr × gradient. Adjust the learning rate and click Step.
Installation
npx shadcn@latest add https://craftbits.dev/r/lr-crash-test.jsonUsage
import { LRCrashTest } from "@craft-bits/viz/lr-crash-test";
<LRCrashTest />Start with a different learning rate so the visitor sees explosion immediately:
<LRCrashTest defaultLearningRate={1.1} />Subscribe to step events for an external loss-vs-step chart:
<LRCrashTest
onStep={(history) => {
/* read history.map((h) => h.loss) */
}}
/>Understanding the component
- The plot. A 540 × 320 SVG renders
x ∈ [-8, 8]againstL ∈ [0, 28]. The parabola is drawn as a 160-segment polyline, faded so it sits behind the action. A dashed crosshair at the origin marks the global minimum. - The dot. The blob at
(x, L(x))is the optimizer's current position. Its colour reflects the current phase. A soft glow under the dot is driven by the same fill colour at lower opacity through a Gaussian-blur filter. - Step. The action button computes the next position from the gradient and the current learning rate, then springs the dot to it via
motion'sanimate()driving raw SVG attributes — no re-render per frame. - Phase machine. The walker classifies its own behaviour from the trajectory:
observeat the start,safewhile the loss is shrinking,edgewhen the dot crosses zero in the oscillation band,explodeonce|x|is growing under a large learning rate, andinsightonce the learner has seen an explosion and then dialled the rate back below the stability boundary. - Trail. Every visited position is plotted as a small dot, the most recent fading in opacity from a series of earlier ghosts. A dashed polyline connects them so the eye reads the trajectory at a glance.
- In-SVG slider. The learning-rate dial lives below the plot. Tick marks at
.1 / .3 / .5 / .7 / 1.0 / 1.2 / 1.5. Everything past1.0is the danger zone — the track recolours at low opacity, and the1/L_maxboundary is labelled inline. - Step arithmetic. Right after each step, a small badge across the top of the plot shows the literal calculation. This makes the math live for the learner instead of hiding it behind animation.
- Reduced motion. Under
prefers-reduced-motion: reduce, the step animation collapses to an instant attribute set, the explosion shake disables, and the observe / edge / explode pulses all stop.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
defaultX | number | 5 | Initial x position on the parabola. Reset returns here. |
defaultLearningRate | number | 0.3 | Initial slider value. Clamped to [0.01, 1.5]. |
explodeThreshold | number | 20 | ` |
transition | Transition | SPRINGS.smooth | Override the step animation transition. |
onStep | (history) => void | — | Fires after each step, post-animation, with full history. |
onReset | () => void | — | Fires when the user clicks Reset. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The plot SVG is
role="img"with anaria-labelsummarising the currentx, loss, learning rate, and step count. - The in-SVG learning-rate slider is
role="slider"with min/max/now/text set, full keyboard support (arrow keys for plus/minus a step, Shift for ten-step jumps, Home and End for bounds), and a visible focus ring. - Space or Enter on the focused slider triggers Step — the entire crash test is operable without a mouse.
- A live region below the buttons announces position, loss, learning rate, and phase after each step. It mutes while the slider is being dragged to avoid noisy updates.
- The narration paragraph also has
aria-live="polite"and reads as plain prose; it is the canonical explanation for each phase. - Colour is never the only signal — the phase is also encoded in the narration prose and the live-region status text.
- Motion respects
prefers-reduced-motion: reduce— the dot snaps instantly, the explosion shake disables, and every idle / warning pulse collapses.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/math/LRCrashTest.tsx). The source was a tightly bundled lesson component — it consumedSvgLabelandChallengeBtnfrom the lesson chrome, depended on the per-track lesson palette tokens, and inlined its own ad-hoc spring names. The viz extract drops the lesson chrome, remaps the colour palette tovar(--cb-*)semantic tokens so consumer themes repaint freely, and re-keys the step animation to the canonicalSPRINGS.smoothso all motion comes from the same place as every other craft-bits component.