Optimizer

Note

The eOn optimizers skip a line search when taking the steepest-descent step.

There are several other ways in which the eOn implementations differ from a standard optimizer software suite[1] . Some prominent reasons are:

  • They are written for the Nudged elastic band, whose energy surface has no closed form

    • Especially in the global band optimization approach [OPT_STH08].

  • They are specialized for atomic systems

  • The optimizers only see the moving atoms, the frozen atoms are omitted before being passed to the optimizer

Configuration

[Optimizer]
pydantic model eon.schema.OptimizerConfig[source]
Fields:
  • opt_method (Literal['box', 'cg', 'qm', 'lbfgs', 'fire', 'sd', 'xtsci'])

  • opt_backend (Literal['eon', 'rgmin'])

  • convergence_metric (Literal['norm', 'rms', 'max_atom', 'max_component'])

  • converged_force (float)

  • max_move (float)

  • time_step (float)

  • max_iterations (int)

field opt_method: Literal['box', 'cg', 'qm', 'lbfgs', 'fire', 'sd', 'xtsci'] = 'cg'
Options:
  • box: Optimizes the atom positions and box using quickmin

  • cg: Conjugate gradient

  • qm: Quickmin

  • lbfgs: Limited Memory Broyden-Fletcher-Goldfarb-Shanno QuasiNewton optimizer

  • fire: Fast inertial relaxation engine

  • sd: Steepest descent

  • xtsci: rgmin engine (formerly xtsci-optimize). Pick the solver with [Xtsci] method. Needs a build that links rgmin (-Dwith_rgmin).

The optimization method to use.

field opt_backend: Literal['eon', 'rgmin'] = 'eon'
Options:
  • eon: eOn’s own optimizers (default)

  • rgmin: rgmin sessions: lbfgs is rgmin L-BFGS, fire is FIRE, cg is Polak-Ribiere CG, sd is steepest descent, qm is quick-min. Needs a build that links rgmin (-Dwith_rgmin). Applies to every objective whose gradient is the derivative of its energy (relaxations, the per-image IDPP path, BGSD); the nudged elastic band, the collective IDPP band and min-mode translation steps stay on eOn’s optimizers. [LBFGS] lbfgs_memory and the [Xtsci] session keys still apply.

Who runs the lbfgs, fire, cg, sd and qm optimizers.

field convergence_metric: Literal['norm', 'rms', 'max_atom', 'max_component'] = 'norm'
Options:
  • norm: Euclidean ||F||_2 of the free-atom force vector (default)

  • rms: ||F||_2 / sqrt(3 N_free), OPTIM MYLBFGS RMS. Opt-in.

  • max_atom: The maximum force on any non-frozen atom

  • max_component: The maximum force on any non-frozen degree of freedom

The metric to use to determine when an optimization is complete.

field converged_force: float = 0.01

When the convergence_metric is smaller than this value (eV/A), the structure is considered minimized.

field max_move: float = 0.2

Maximum distance that an atom may be moved in a single optimization step (Angstroms).

field time_step: float = 1.0

The dynamical timestep for the quickmin algorithm (fs).

field max_iterations: int = 1000

The maximum number of optimization iterations that will be performed.

Each of the optimizer methods have their own settings as well.

LBFGS

[LBFGS]
pydantic model eon.schema.LBFGSConfig[source]
Fields:
  • lbfgs_memory (int)

  • lbfgs_inverse_curvature (float)

  • lbfgs_max_inverse_curvature (float)

  • lbfgs_auto_scale (bool)

  • lbfgs_angle_reset (bool)

  • lbfgs_distance_reset (bool)

  • lbfgs_curvature (Literal['reset', 'skip', 'damped', 'cautious'])

  • lbfgs_project_rigid (bool)

  • lbfgs_secant (Literal['standard', 'zhangxu'])

  • lbfgs_precon (Literal['none', 'exp', 'c1', 'lindh', 'lindh_full', 'pair', 'pair_abs', 'pair_full', 'fischer', 'schlegel', 'swart'])

  • lbfgs_step (Literal['lbfgs', 'newton', 'rfo'])

  • lbfgs_h0 (Literal['sy_yy', 'ss_sy', 'adaptive'])

  • lbfgs_accept (Literal['none', 'energy', 'nonmonotone'])

  • lbfgs_extra_updates (int)

  • lbfgs_cautious_eps (float)

  • lbfgs_cautious_alpha (float)

  • lbfgs_precon_A (float)

  • lbfgs_precon_mu (float)

  • lbfgs_precon_rcut (float)

field lbfgs_memory: int = 20

Number of previous gradients and positions to store for the LBFGS method.

field lbfgs_inverse_curvature: float = 0.01

Initial inverse curvature value for LBFGS.

field lbfgs_max_inverse_curvature: float = 0.0

Maximum inverse curvature value for LBFGS.

field lbfgs_auto_scale: bool = True

If true, auto-scale the inverse curvature in LBFGS.

field lbfgs_angle_reset: bool = True

If true, reset the LBFGS angle.

field lbfgs_distance_reset: bool = True

If true, reset the LBFGS distance.

field lbfgs_curvature: Literal['reset', 'skip', 'damped', 'cautious'] = 'reset'
Options:
  • reset: wipe L-BFGS memory (ASE-style default)

  • skip: skip that pair when s·y < 0.2 sᵀ B0 s

  • damped: Powell 1978 damping, ŷ = θy+(1-θ)B0 s

  • cautious: Li-Fukushima 2001, keep the pair only if s·y >= ε ||s||² ||g||^α

How to treat a pair with insufficient curvature.

field lbfgs_project_rigid: bool = False

Project translation and rotation out of the L-BFGS force and step. Isolated clusters only.

field lbfgs_secant: Literal['standard', 'zhangxu'] = 'standard'

Secant pair: standard y = g_{k+1}-g_k, or Zhang-Xu modified secant using the two energies.

field lbfgs_precon: Literal['none', 'exp', 'c1', 'lindh', 'lindh_full', 'pair', 'pair_abs', 'pair_full', 'fischer', 'schlegel', 'swart'] = 'none'

Two-loop H0 metric. none is H0 I. exp/c1 is Packwood-Kermode (JCP 2016). lindh is the Lindh 1995 stretch model. pair is the Mones 2018 positive-definite pair Hessian of LJ or Morse. pair_full is the signed pair Hessian for Newton/RFO.

field lbfgs_step: Literal['lbfgs', 'newton', 'rfo'] = 'lbfgs'

lbfgs is the two-loop direction. newton is Levenberg-shifted H^{-1} g. rfo is Banerjee/Baker rational function optimization.

field lbfgs_h0: Literal['sy_yy', 'ss_sy', 'adaptive'] = 'sy_yy'

Initial inverse-Hessian scale: Nocedal-Wright 7.20, Barzilai-Borwein-2, or the smaller of the two.

field lbfgs_accept: Literal['none', 'energy', 'nonmonotone'] = 'none'

none takes the two-loop step (ASE / historical eOn). energy refuses a rise. nonmonotone is a Grippo window of the last five values.

field lbfgs_extra_updates: int = 0

Al-Baali extra-updates: reuse the newest pair this many extra times in the two-loop recursion.

field lbfgs_cautious_eps: float = 1e-06

Li-Fukushima ε in s·y >= ε ||s||² ||g||^α.

field lbfgs_cautious_alpha: float = 0.01

Li-Fukushima α in s·y >= ε ||s||² ||g||^α.

field lbfgs_precon_A: float = 3.0

Packwood Exp stiffness A in μ exp(-A (r/r_nn - 1)).

field lbfgs_precon_mu: float = 1.0

Packwood pair-preconditioner scale μ.

field lbfgs_precon_rcut: float = 0.0

Pair-preconditioner cutoff. 0 means 2 r_nn.

A classic dimer ([Dimer] improved = false) reports |C_tau| as a known first-step scale when the mode is negative. L-BFGS keeps H0 = 1/|C_tau| when that step fits in max_move, and also when the mode is milder than 30 eV/Ų and the cap would otherwise set the length. A steeper mode whose uncapped step exceeds max_move takes one finite-difference probe of the effective force. Improved dimer and Lanczos do not report |C_tau|. Those searches climb on the effective force, so the first step is the probe and max_move is only the cap. Later steps still use the [LBFGS] secant scale. A build that rotates the mode in rgsaddle uses the same rule. improved = false reports |C_tau|, and the improved dimer and Lanczos do not.

QuickMin

[QuickMin]
pydantic model eon.schema.QuickMinConfig[source]
Fields:
  • time_step (float)

  • qm_steepest_descent (bool)

field time_step: float = 1.0

Time step for QuickMin, in femtoseconds.

field qm_steepest_descent: bool = False

If true, use the steepest descent method for QuickMin.

FIRE

[FIRE]
pydantic model eon.schema.FIREConfig[source]
Fields:
  • time_step (float)

  • time_step_max (float)

field time_step: float = 1.0

Time step for FIRE, in femtoseconds.

field time_step_max: float = 1.0

Maximum time step for FIRE, in femtoseconds.

CG

[CG]
pydantic model eon.schema.CGConfig[source]
Fields:
  • cg_no_overshooting (bool)

  • cg_knock_out_max_move (bool)

  • cg_line_search (bool)

  • cg_line_converged (float)

  • cg_max_iter_before_reset (int)

  • cg_max_iter_line_search (int)

field cg_no_overshooting: bool = False

If true, prevent overshooting in CG.

field cg_knock_out_max_move: bool = False

If true, limit the maximum move in CG.

If true, perform a line search in CG.

field cg_line_converged: float = 0.1

Convergence criterion for the line search in CG.

field cg_max_iter_before_reset: int = 0

Maximum number of iterations before reset in CG.

Maximum number of iterations for the line search in CG.

SD

[SD]
pydantic model eon.schema.SDConfig[source]
Fields:
  • sd_alpha (float)

  • sd_two_point (bool)

field sd_alpha: float = 0.1

Alpha value for SD.

field sd_two_point: bool = False

If true, use the two-point method in SD.

rgmin backend

eOn’s minimizers can run inside rgmin, the Rust minimizer library (formerly xtsci-optimize), instead of eOn’s own C++ code. Two keys select it:

  • [Optimizer] opt_backend = rgmin keeps opt_method and hands the step to the rgmin solver of the same name.

  • [Optimizer] opt_method = xtsci runs whichever rgmin solver [Xtsci] method names, including those eOn has no native version of (bfgs, newton, rfo, dogleg, fire2, …).

opt_method

rgmin solver under opt_backend = rgmin

lbfgs

lbfgs

fire

fire

cg

polak_ribiere

sd

steepest

qm

quickmin

[Optimizer]
opt_method = lbfgs
opt_backend = rgmin

Both need a client that links rgmin. -Dwith_rgmin=auto (the default) links an rgmin found by pkg-config and changes nothing until an input selects it; -Dwith_rgmin=enabled makes a missing rgmin a configure error. -Dwith_xtsci=true is the deprecated spelling of enabled. The client needs rgmin 0.4.0 or newer, installed with

cargo cinstall --release --features capi --prefix=$PREFIX
# cinstall regenerates rgmin.h; the C++ client needs the curated header
cp include/rgmin.h $PREFIX/include/
cp -r include/dlpack $PREFIX/include/

eonclient --features lists rgmin minimizers when it is linked. A client without rgmin rejects opt_backend = rgmin with an error naming -Dwith_rgmin.

opt_backend only applies where the optimizer minimizes an energy whose gradient is the force: relaxations in every job, NEB endpoint minimization, the per-image IDPP path and BGSD. The NEB band, the collective IDPP band and the min-mode translation step are not minimizations of an energy, so they keep eOn’s optimizer; the rgsaddle section below covers the band.

Matter evaluates every energy and force rgmin asks for, so total_force_calls counts the same calls as with opt_backend = eon. The host loop applies converged_force, max_iterations and max_move (as eOn’s largest per-atom move) as before. rgmin reads time_step for fire and qm, [LBFGS] lbfgs_memory for the memory, and the [Xtsci] or [LBFGS] preconditioner and acceptance keys described below. The other [FIRE], [CG], [QuickMin] and [SD] keys belong to eOn’s own solvers and do not reach rgmin.

Xtsci

opt_method = xtsci selects the rgmin engine. The solver inside that engine is [Xtsci] method. The adapter holds an rgmin_solver_t session; each step() is one outer iteration so L-BFGS pairs and NLCG conjugacy survive the host loop.

When eindir-core is on the pkg-config path, the same backend borrows an eindir_objective_t around the ObjectiveFunction. The adapter checks the eindir ABI stamp, the gradient feature bit, and the semantic descriptor (eV, angstrom, analytic forces) before dispatch. Optimizer::run then calls rgmin_minimize_eindir unless the step needs a host Hessian. step() stays one rgmin_solver_t iteration.

[Xtsci] qn_step, precon, and accept are the first-class knobs for an L-BFGS session. They map onto rgmin_solver_set_qn_step, rgmin_solver_step_hess_fg, and rgmin_solver_set_accept. Energy and forces share one potential->force() call. accept = none takes the maxmove-clipped step (one geometry). nonmonotone is the Grippo window used by the rematch INI. The pair / Lindh matrix is still built in eOn. Two-loop plus that matrix is (H_0 = P^{-1}); qn_step = newton is regularized Newton on the same P. method = newton is a different, memoryless Newton solver.

The native [LBFGS] optimizer is unchanged. lbfgs_step, lbfgs_precon, and lbfgs_accept still drive it, and the rgmin adapter falls back to them when the [Xtsci] fields are left at their defaults.

[Optimizer]
opt_method = xtsci

[Xtsci]
method = lbfgs
qn_step = newton
precon = pair
accept = nonmonotone
pydantic model eon.schema.XtsciConfig[source]
Fields:
  • method (Literal['lbfgs', 'bfgs', 'sr1', 'sr2', 'newton', 'rfo', 'steepest', 'adam', 'pso', 'polak_ribiere', 'fletcher_reeves', 'hestenes_stiefel', 'dai_yuan', 'conjugate_descent', 'hager_zhang', 'liu_storey', 'fr_pr', 'fire', 'bb', 'dogleg', 'fire2', 'quickmin'])

  • qn_step (Literal['lbfgs', 'newton', 'rfo'])

  • precon (Literal['none', 'pair', 'pair_abs', 'pair_full', 'exp', 'c1', 'lindh', 'lindh_full', 'fischer', 'schlegel', 'swart'])

  • accept (Literal['none', 'energy', 'nonmonotone'])

  • highs (bool)

  • manifold (Literal['euclidean', 'rigid_quotient', 'mw_rigid', 'sphere', 'so3', 'stiefel', 'se3'])

field method: Literal['lbfgs', 'bfgs', 'sr1', 'sr2', 'newton', 'rfo', 'steepest', 'adam', 'pso', 'polak_ribiere', 'fletcher_reeves', 'hestenes_stiefel', 'dai_yuan', 'conjugate_descent', 'hager_zhang', 'liu_storey', 'fr_pr', 'fire', 'bb', 'dogleg', 'fire2', 'quickmin'] = 'lbfgs'

opt_method = xtsci selects the engine. This field selects the method the engine runs. Tokens match rgmin_method_t.

Quasi-Newton:
  • lbfgs: two-loop L-BFGS (Nocedal-Wright 7.4)

  • bfgs: dense inverse-BFGS

  • sr1: inverse SR1

  • sr2: SR2 Hessian update

Hessian (pair Hessian from eOn, rgmin_minimize_hess):
  • newton: Levenberg-shifted Newton

  • rfo: Banerjee / Baker rational function optimization

First-order:
  • steepest: d = -g

  • adam: Kingma-Ba Adam with a line search

  • pso: particle swarm

  • fire: Bitzek FIRE (one fused force per geometry)

  • fire2: Guénolé FIRE 2.0

  • bb: Barzilai-Borwein spectral step

  • quickmin: quick-min, the velocity projected on the force (rgmin ABI minor 29)

Trust-region Hessian (pair Hessian from eOn):
  • dogleg: Powell dogleg on the host pair matrix

Nonlinear CG (conjugacy is the method token):
  • polak_ribiere, fletcher_reeves, hestenes_stiefel, dai_yuan, conjugate_descent, hager_zhang, liu_storey, fr_pr

Solver inside the rgmin engine (opt_method = xtsci).

field qn_step: Literal['lbfgs', 'newton', 'rfo'] = 'lbfgs'

Only used when method = lbfgs. lbfgs is two-loop with optional precon as (H_0 = P^{-1}). newton / rfo take that same host matrix as the step (eOn rematch).

How an L-BFGS session uses a host Hessian (rgmin_qn_step_t).

field precon: Literal['none', 'pair', 'pair_abs', 'pair_full', 'exp', 'c1', 'lindh', 'lindh_full', 'fischer', 'schlegel', 'swart'] = 'none'

pair is the Mones PSD clip. pair_full keeps signed curvature. lindh_full / fischer / schlegel / swart are Cartesian model Hessians (B^T k B). The matrix is assembled in eOn and passed through rgmin_solver_step_hess.

Host pair / model Hessian kind. Built in eOn, not in xtsci.

field accept: Literal['none', 'energy', 'nonmonotone'] = 'none'

none takes the maxmove-clipped step (one oracle). energy refuses a rise. nonmonotone is the Grippo window of five.

How the session takes a proposed step (rgmin_accept_t).

field highs: bool = False

When true, xtsci asks HiGHS for the step: Newton QP on the host pair Hessian if one is passed, otherwise two-loop plus boxes. Needs --features highs on the rgmin build.

HiGHS feasible-set step (rgmin_solver_set_highs).

field manifold: Literal['euclidean', 'rigid_quotient', 'mw_rigid', 'sphere', 'so3', 'stiefel', 'se3'] = 'euclidean'

Euclidean is the default. Isolated molecules use rigid_quotient (Sella Cartesian (R^{3N}/SE(3))) or mw_rigid (Page-McIver / Sella IRC Eckart). sphere is (S^{n-1}), so3 a row-major 9-vector, stiefel is (mathrm{St}(n,1)), se3 is rotation then translation (length 12).

Embedded manifold (rgmin_manifold_t).

Rgsaddle band and min-mode

rgsaddle, the Rust saddle-search library, steps the NEB band and rotates the min-mode dimer when the client is built with -Dwith_rgsaddle=true (rgsaddle 0.2.0 or newer, found by pkg-config). There is no runtime switch: in such a build the existing keys pick the rgsaddle path.

Input

Build without rgsaddle

Build with -Dwith_rgsaddle=true

[Nudged Elastic Band] opt_method = lbfgs, fire or xtsci, non-geometric spring

eOn optimizer on the band objective

rgsaddle band session

[Nudged Elastic Band] opt_method = cg, sd, qm, or a geometric spring

eOn optimizer

eOn optimizer

[Saddle Search] min_mode_method = dimer, lanczos or xtsci

eOn dimer or Lanczos (xtsci is an error)

rotation in an rgsaddle session

min_mode_method = davidson, gprdimer

eOn

eOn

eonclient --features lists rgsaddle band and min-mode in such a build. In the band session [Xtsci] method = fire is the band stepper and method = lbfgs selects the L-BFGS session; opt_method = lbfgs and fire pick the matching stepper. [Optimizer] refine_* does not apply to a band stepped by rgsaddle. Matter still owns the images, and the NEB loop still owns climbing policy, resampling, and the spline. The surface callback returns a status. The first band request carries every image. Later requests carry the interior images, or one image. When the coordinates already match, that image stays put and its energy and forces stay with it. The images that move go out as one batch. The client resends the band only after a reparameterization, or after another move outside the session. Every image evaluation goes through Matter, so force_calls_neb keeps its meaning.

min_mode_method = xtsci rotates the lowest mode in that session with a zero translation cap. The min-mode callback reads the centre from its cached evaluation and runs a displaced probe on a copy. The min-mode saddle search still climbs with the selected optimizer. min_mode_method = dimer and lanczos rotate in the same session, lanczos as a Lanczos session. Davidson and the GP dimer keep their own solvers. Against rgsaddle ABI minor 5 or newer one session serves every step of the search: the client moves it to each new centre with the gradient it already has, so the centre costs no extra call, and the previous mode seeds the next rotation.

The session takes its settings from the dimer and Hessian keys:

rgsaddle setting

eOn key

Conversion

dr

[Main] finite_difference

Angstrom; 0.001 when not positive

rotation_tol

[Dimer] torque_min

none

rotation_angle_tol

[Dimer] converged_angle

degrees to radians (ABI minor 5)

max_rotations

[Dimer] rotations_max

at least 1

difference

[Hessian] fd_scheme

one_sided is forward; central and fourth are central (ABI minor 5)

method

[Xtsci] method

lbfgs gives L-BFGS; any other gives FIRE, the fast inertial relaxation engine

krylov_dim

none

12

rgsaddle has no fourth-order stencil, so fd_scheme = fourth gives central differences here. [Dimer] rotation_angle is a trial angle of the host dimer and does not reach the session.

References

[OPT_STH08]

Daniel Sheppard, Rye Terrell, and Graeme Henkelman. Optimization methods for finding minimum energy paths. The Journal of Chemical Physics, 128(13):134106, April 2008. doi:10.1063/1.2841941.