Why the line search terminates: brackets, cubics, and a guard#
Every solver in this crate that follows a descent direction d from a
point x reduces the problem to one dimension. Write the restriction
of the objective along the ray as phi(a) = f(x + a d), so
phi’(0) = g . d is negative for any descent direction. The line
search’s job is to pick a step a that provably makes progress
without spending the caller’s evaluation budget, and every claim below
is about where those two guarantees come from.
1 The two Wolfe conditions#
Sufficient decrease (Armijo) demands phi(a) <= phi(0) + c1 a phi’(0)
with 0 < c1 < 1: the step must capture at least a c1 fraction of the
decrease the initial slope promised. Because phi’(0) < 0 and a > 0,
the right-hand side sits strictly below phi(0), so an accepted point is
a real improvement, not a rounding story. That one-line consequence is
the Lean theorem Rgmin.armijo_strict in
proofs/lean/Rgmin/Zoom.lean.
The curvature condition demands abs(phi’(a)) <= c2 abs(phi’(0)) with
c1 < c2 < 1: the slope must have flattened enough that the quasi-Newton
update built from this step carries curvature information. Nocedal and
Wright’s algorithms 3.5 and 3.6 (the bracket and the zoom) find a point
satisfying both, and src/linesearch/zoom.rs is a direct
implementation.
2 The bracket and the zoom#
The outer loop (wolfe_search) expands a trial step until it either
satisfies both conditions, overshoots (value above the Armijo line, or
above the previous trial), or the slope turns nonnegative. Overshoot
and slope-reversal both certify that a Wolfe point lies between the
last two trials: that pair becomes the bracket [lo, hi], ordered so
lo carries the lower value.
The zoom then shrinks the bracket. Each iteration proposes a trial
inside [lo, hi], evaluates it, and replaces whichever end the Wolfe
tests disqualify. The proposal is where the evaluation budget is won
or lost, and the guard is where termination is proved.
3 The cubic Hermite proposal#
At the two ends the zoom holds four numbers: phi(lo), phi’(lo), phi(hi), phi’(hi). There is exactly one cubic through both points with both slopes, and its interior minimizer has the closed form (Nocedal and Wright eq. 3.59)
d1 = phi’(lo) + phi’(hi) - 3 (phi(lo) - phi(hi)) / (lo - hi) d2 = sign(hi - lo) sqrt(d12- phi’(lo) phi’(hi)) t = hi - (hi - lo) (phi’(hi) + d2 - d1) / (phi’(hi) - phi’(lo) + 2 d2)
On a well-behaved function the cubic model is third-order accurate, so
t lands close to the true minimizer and the zoom converges in a
handful of evaluations; the measured effect in anneal was 46 to 43
force calls per hop across the whole hopping battery, purely from this
proposal replacing bisection. When the discriminant goes negative or a
slope is non-finite the code falls back to the quadratic through
phi(lo), phi’(lo), phi(hi), and below that to bisection: the ladder
degrades in accuracy, never in safety.
4 The guard is the termination proof#
A cubic through awkward data can propose a point arbitrarily close to either end, and a zoom that accepts such proposals can stall. The code clamps every proposal into the middle eighty percent of the bracket: no closer to an end than a tenth of the width. Two exact rational facts follow, proved in Zoom.lean:
Rgmin.guarded_above/Rgmin.guarded_below: the clamp really does land in the interior band.Rgmin.zoom_shrinks: whichever end the trial replaces, the surviving bracket is at most9/10of the old width.
The width therefore decays geometrically (Rgmin.width_envelope is
the bound, width_envelope_tendsto takes it to zero in the limit),
and the evaluation budget of the zoom is logarithmic in the
requested tolerance regardless of what the cubic does. The Rust test
the_cubic_zoom_keeps_the_evaluation_budget in
tests/lbfgs_state.rs holds the crate to that budget on a quadratic
bowl.
5 What the accept layer adds#
Above the line search, src/accept.rs offers three policies: take the
clipped step (one oracle call), refuse any energy rise (Accept::Energy,
up to ten halvings), or the Grippo-Lampariello-Lucidi nonmonotone
window. The fallback steepest-descent probe inside the energy policies
faces the same energy test as the step it replaces: a fallback that
also fails reports the position unmoved, and the caller’s stall
machinery owns what happens next. The test
accept_energy_uphill_does_not_take_ten_steepest_retries pins the
oracle count of that path.