adaptive step-size control
A driver does not hold one fixed speed for the whole trip — they slow to a crawl on the hairpin bends and open up on the empty straightaway. A fixed-step ODE solver is like driving at one speed everywhere: wastefully slow where the solution is calm, dangerously fast where it changes quickly. Adaptive step-size control lets the solver vary its step h on the fly, big where the solution is smooth, small where it is sharp, so it does the least work for a target accuracy.
The mechanism: at each step, estimate the local error (typically from an embedded Runge-Kutta pair, or from the gap between a predictor and corrector). Compare that estimate err to a user TOLERANCE tol. If err is below tol, ACCEPT the step and, since you had room to spare, ENLARGE h for the next one; if err exceeds tol, REJECT the step and REDO it with a smaller h. The new step is chosen from a formula like h_new = h * safety * (tol/err)^(1/(p+1)), where p is the method's order and 'safety' is a factor slightly below 1 to avoid repeated rejections. The tolerance is usually a mix of absolute and relative parts, tol = atol + rtol*|y|, so it scales sensibly whether the solution is large or near zero.
Adaptivity is what makes a solver both ROBUST and EFFICIENT: it automatically takes thousands of careful steps through a sudden spike and a handful of giant steps through a long quiet stretch, all to hold the error near the tolerance you asked for. The honesty: the controller steers the LOCAL (per-step) error, which only loosely bounds the GLOBAL error — a tight tolerance does not guarantee a globally tight answer on a long or chaotic run. Set tolerances too loose and the answer is junk; too tight and you drown in tiny steps and round-off. And adaptive explicit methods still cannot rescue a stiff problem; there the step gets clamped tiny by STABILITY, not accuracy, and you must switch to an implicit solver.
Integrating a swinging pendulum that occasionally gets a sharp kick: between kicks the solution is smooth, so the controller stretches h to take long strides; the instant a kick hits, the error estimate spikes above tolerance, the step is rejected and retried with h cut by maybe 10x, and the solver inches through the transient before opening back up. The user sets tol = 1e-6 and never touches h by hand.
Big steps where smooth, tiny steps where sharp — error held near the requested tolerance.
The controller bounds the per-step LOCAL error, not the global error, so a small tolerance is necessary but not sufficient for a globally accurate result. And on a stiff problem an explicit solver's step is throttled by stability, not accuracy — adaptivity then just produces a tiny step forever; switch to an implicit method.