Quick-min#
Quick-min keeps a velocity and damps it by a projection before an
explicit Euler step. Write F = -∇f and Δt for the time step. The
reference update is
v ← (v · F / |F|²) F when v · F ≥ 0 and |F| > 0
v ← 0 otherwise
v ← v + Δt F
Δx = Δt v
quickmin_baseline is that update and nothing else. A negative
projection takes the second branch, so the velocity that enters the
Euler kick has no component opposing F. A zero force has no
direction and takes the same branch.
The session adds three pieces that the other first-order methods already own.
The displacement cap is Solver::set_atom_maxmove or
Control::maxmove. When it shortens Δx, the velocity is multiplied
by |Δx taken| / |Δx proposed|. That is the ratio FIRE uses after its
own cap. The next projection then sees the motion that happened. On
E = ½ (y - 0.2 x)² + 0.02 x²
from (2, 0) with Δt = 0.2 and cap 0.08, eighty reference steps
that keep the uncapped velocity stay above 10⁻⁷, and the same steps
with the rescale fall below 10⁻⁸.
The time step uses FIRE’s factors: n_min = 5, f_inc = 1.1,
f_dec = 0.5, Δt_max = 2.5 Δt₀. More than n_min steps with
F · v > 0 grow Δt. F · v < 0 shrinks it, down to 10⁻¹²; the
velocity on that branch is already zero from the reference rule.
F · v = 0 leaves Δt alone. A resting velocity is not an uphill
step, and halving the caller’s Δt before any motion only slows the
opening. On E = ½ x² from x = 1 with Δt₀ = 0.05, the growing
step reaches |x| < 10⁻³ in fewer iterations than the fixed step.
Solver::set_quickmin_adapt(false) selects the fixed reference step.
A cell block is an index at. Coordinates [0, at) and [at, n)
each take the reference replacement on their own force, then the Euler
kick uses the shared Δt. A cell velocity that opposes the stress is
zeroed even when the atomic alignment keeps v · F positive for the
whole vector. One joint projection would replace both blocks by a
single component along the joint force and spend atomic momentum on
the opposing cell direction. On E = ½ (x² + c²) at (-1, 1) with
v = (3, 1), F = (1, -1) and Δt = 0.1, the split step reaches a
lower value than the joint projection, and the cell velocity after the
replacement is only the Euler kick Δt F_c.
Solver::set_quickmin_cell sets the index. The C entry is
RGMIN_QUICKMIN and rgmin_solver_set_quickmin_cell (ABI minor 29).
Options and what the benchmark keeps#
Solver::set_quickmin_options takes QuickMinOptions. rgmin() is
the session above. eon() is eOn’s Quickmin::step: fixed Δt, one
projection, and a capped step that leaves the velocity at its
uncapped value. per_atom() projects each xyz triple of the atomic
block on its own force, so one atom moving against its force stops
without stopping the others. curvature_cap = Some(c) bounds Δt by
c / sqrt(λ̂), with λ̂ = |y| / |s| from the last step’s force
difference.
On E = λ x² / 2 with the projection inactive, the step is the
symplectic Euler map with characteristic polynomial
r² - (2 - h) r + 1, h = Δt² λ. Its roots stay on the unit circle for
0 ≤ h ≤ 4, so Δt < 2 / sqrt(λ_max) is the stability limit. A step
from rest moves Δt² F and lowers the energy exactly when h < 2;
with λ̂ equal to the mode’s curvature, c < sqrt(2) keeps that.
With a kept velocity v = c F the step length along F is
Δt (c + Δt), and c grows while the walker stays on one side, so
the eOn step can raise the energy even under Δt² λ_max < 2
(validation/quickmin.py). The projection itself never lengthens
v and leaves no component along -F
(formal/RgminContracts/QuickMin.lean); in binary64 it can lengthen
v by at most B(n) - 1 relative, 7 machine epsilons for n = 6
(validation/quickmin.sollya).
bench/atomistic (rgpot potentials, 5 starts each, per-atom cap 0.2,
fmax < 0.01), mean force calls and converged starts:
options |
LJ38 |
Pt7 island |
Al slab |
|---|---|---|---|
|
2001 (0 of 5) |
18.6 |
24.6 |
|
1344 (4 of 5) |
19.6 |
27.6 |
|
645 (5 of 5) |
32.0 |
28.2 |
|
1167 (5 of 5) |
20.0 |
27.6 |
FIRE |
411 |
50.6 |
60.2 |
FIRE 2.0 (Guénolé 2020) |
871 |
26.0 |
33.8 |
L-BFGS |
169 |
10.0 |
14.0 |
The eOn options never converge LJ38 from random packings in 2000
steps; the FIRE time step is what converges it. The per-atom
projection halves the LJ38 calls and costs 12 calls on the seven-atom
island, so it is an option, not the default. A restart or a backtrack
after an energy rise, a ten-fold Δt_max, and dropping the clamp
rescale moved the LJ38 mean by at most 9 calls and the others by at
most 0.4, and none of them converged another start; they were removed.