"use client"; import { motion } from "framer-motion"; import { COLORS, VizFrame } from "./common"; /** * Four small panels showing canonical training-curve patterns: * 1. healthy fit * 2. overfit * 3. underfit * 4. data leakage / unstable val */ function curve(N: number, fn: (t: number) => number) { return Array.from({ length: N }, (_, i) => fn(i / (N - 1))); } function CurvePanel({ title, diagnosis, trainFn, valFn, width, height, }: { title: string; diagnosis: string; trainFn: (t: number) => number; valFn: (t: number) => number; width: number; height: number; }) { const padX = 26; const padY = 50; const innerW = width - padX * 2; const innerH = height - padY - 50; const N = 80; const sx = (i: number) => padX + (i / (N - 1)) * innerW; const sy = (v: number) => padY + (1 - v) * innerH; const train = curve(N, trainFn); const val = curve(N, valFn); return ( {title} `${sx(i)},${sy(v)}`).join(" L ")}`} fill="none" stroke={COLORS.accent} strokeWidth={1.5} initial={{ pathLength: 0 }} animate={{ pathLength: 1 }} transition={{ duration: 1.0 }} /> `${sx(i)},${sy(v)}`).join(" L ")}`} fill="none" stroke={COLORS.honey} strokeWidth={1.5} initial={{ pathLength: 0 }} animate={{ pathLength: 1 }} transition={{ duration: 1.0, delay: 0.2 }} /> {/* Legend */} train val {diagnosis} ); } export function FailureModes({ width = 980, height = 380, }: { width?: number; height?: number; }) { const cols = 4; const padX = 14; const gap = 12; const cellW = (width - padX * 2 - gap * (cols - 1)) / cols; const cellH = height - 30; const panels = [ { title: "Healthy", diagnosis: "train and val both fall, gap small.", trainFn: (t: number) => Math.max(0.07, 0.9 * Math.exp(-t * 3.6)), valFn: (t: number) => Math.max(0.12, 0.95 * Math.exp(-t * 3.0) + 0.04), }, { title: "Overfit", diagnosis: "val curls back up — regularise, more data.", trainFn: (t: number) => Math.max(0.04, 0.9 * Math.exp(-t * 3.8)), valFn: (t: number) => Math.max(0.18, 0.95 * Math.exp(-t * 2.5) + 0.55 * Math.max(0, t - 0.45) ** 1.7), }, { title: "Underfit", diagnosis: "both stuck high — bigger model or longer schedule.", trainFn: () => 0.55, valFn: () => 0.6, }, { title: "Leakage / unstable", diagnosis: "val unrealistically low or wild — check splits.", trainFn: (t: number) => 0.2 + 0.5 * Math.exp(-t * 4), valFn: (t: number) => 0.05 + 0.05 * Math.sin(t * 14) + 0.03 * Math.sin(t * 31), }, ]; return ( {panels.map((p, i) => ( ))} ); }