mod warm_lbfgs

module warm_lbfgs

Quasi-Newton relaxation whose curvature persists between calls. Limited-memory quasi-Newton relaxation whose curvature survives between calls.

A hopping chain relaxes thousands of times, each from a perturbation of an already-relaxed structure, so the curvature at a new start resembles the curvature at the old minimum and a minimiser that forgets between calls pays to rediscover it. Since a work ledger charges evaluations rather than seconds, cutting evaluations per relaxation multiplies the hops a budget buys.

Measured status

On a 75-point Lennard-Jones cluster, 400 relaxations from perturbed minima with overlapping pairs repaired first, against SciPy’s L-BFGS-B on the identical protocol:

arm

evals per relaxation

worst final |g|

cold

386.1

1.45e-5

warm

386.1

1.45e-5

L-BFGS-B

273

1.43e-5

Both arms converge and cost 1.4 times a production minimiser. The deepest structure found over the 400 was -388.26 here against -386.84 for the reference, which is a property of which basins the perturbations landed in rather than of the minimisers.

The retained curvature does not survive, and the name is now vestigial

Warm and cold agree to the last digit because the memory is empty at the end of every relaxation: 0 of 400 started with stored curvature. A relaxation terminates by line-search failure at a gradient near 1e-5, short of the 1e-6 tolerance, and that path discards the memory before returning. So the premise this type was built on, that curvature at a new start resembles curvature at the old minimum and is worth carrying, is not something this implementation tests. It is a correct L-BFGS whose warm start never engages.

Two facts from building it do transfer. Backtracking that enforces only sufficient decrease produces curvature pairs describing curvature never measured, and since every direction is built from the stored pairs, one bad pair degrades the whole memory: with Armijo alone, retaining curvature was 1.8 times worse than discarding it, and the strong Wolfe conditions reverse the sign. And a relaxation benchmark has to check the final gradient, since an unrepaired overlap returns a gradient near 1e13 that no line search recovers from, making a cost comparison count failures instead of work.

Structs and Unions

struct WarmLbfgs

L-BFGS with memory that persists across relaxations.

max_pairs: usize

Pairs retained; the usual choice is between five and ten.

gtol: f64

Gradient infinity norm below which a relaxation is converged.

armijo: f64

Armijo sufficient-decrease constant, c1 in the Wolfe conditions.

curvature: f64

Curvature constant, c2. The usual choice for quasi-Newton is 0.9.

max_line_evals: usize

Line-search evaluations attempted before the direction is abandoned.

Implementations

impl WarmLbfgs

Functions

fn forget(&mut self)

Discards the stored curvature.

Called when the chain moves somewhere structurally different, where the retained pairs describe a Hessian that no longer applies.

fn is_empty(&self) -> bool

True when no curvature is stored.

fn len(&self) -> usize

Pairs currently held.

fn minimize<F>(&mut self, x0: ArrayView1<f64>, max_iter: usize, fg: F) -> (f64, Array1<f64>, usize)
where
    F: FnMut(ArrayView1<f64>) -> Option<(f64, Array1<f64>)>

Relaxes x0, calling fg for value and gradient.

fg returns None when the caller’s budget is spent, which ends the relaxation where it stands. Returns the value, the point, and the number of evaluations used.

fn minimize_watched<F, W>(&mut self, x0: ArrayView1<f64>, max_iter: usize, mut fg: F, mut watch: W) -> (f64, Array1<f64>, usize)
where
    F: FnMut(ArrayView1<f64>) -> Option<(f64, Array1<f64>)>,
    W: FnMut(usize, f64) -> bool

Relaxes x0, offering each accepted iterate to watch.

watch receives the iteration index and the value at that iterate, and returning false ends the relaxation there. The point is a caller that stops on a decision rather than on an iteration count: a screening pass exists to answer one question, and the trajectory it produces says when the answer is settled.

The hook sits at the top of the iteration, not inside the line search, so what it sees is always an accepted point with its value and gradient consistent. Stopping mid-search would return a trial step the optimizer had not adopted.

Traits implemented

impl Default for WarmLbfgs