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.

Learning rate crash test on L of x equals x squared.
Step 0. x = 5, Loss: 25.0, Learning rate: 0.30. In progress.

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.

Customize
Crash test
5.0
0.30
20

Installation

npx shadcn@latest add https://craftbits.dev/r/lr-crash-test.json

Usage

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

  1. The plot. A 540 × 320 SVG renders x ∈ [-8, 8] against L ∈ [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.
  2. 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.
  3. Step. The action button computes the next position from the gradient and the current learning rate, then springs the dot to it via motion's animate() driving raw SVG attributes — no re-render per frame.
  4. Phase machine. The walker classifies its own behaviour from the trajectory: observe at the start, safe while the loss is shrinking, edge when the dot crosses zero in the oscillation band, explode once |x| is growing under a large learning rate, and insight once the learner has seen an explosion and then dialled the rate back below the stability boundary.
  5. 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.
  6. 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 past 1.0 is the danger zone — the track recolours at low opacity, and the 1/L_max boundary is labelled inline.
  7. 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.
  8. 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

PropTypeDefaultDescription
defaultXnumber5Initial x position on the parabola. Reset returns here.
defaultLearningRatenumber0.3Initial slider value. Clamped to [0.01, 1.5].
explodeThresholdnumber20`
transitionTransitionSPRINGS.smoothOverride the step animation transition.
onStep(history) => voidFires after each step, post-animation, with full history.
onReset() => voidFires when the user clicks Reset.
classNamestringMerged onto the root via cn().

Accessibility

  • The plot SVG is role="img" with an aria-label summarising the current x, 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 consumed SvgLabel and ChallengeBtn from 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 to var(--cb-*) semantic tokens so consumer themes repaint freely, and re-keys the step animation to the canonical SPRINGS.smooth so all motion comes from the same place as every other craft-bits component.