Energy Minimization¶
Unlike NVE/NVT/NPT, a minimization run isn't selected through numerical_scheme — each minimizer's config file (exaStamp/data/config/config_conjugate_gradient.msp / config_fire.msp) overrides the whole simulation: scenario itself, swapping the default first_iteration/md_trajectory_loop pair for its own. There is no numerical_scheme: entry to set for either; everything else (species, domain, load balancing, force computation, …) is reused unchanged.
Two independent minimizers are available:
| Include | Method | Advances real time? |
|---|---|---|
config_conjugate_gradient.msp |
Fletcher-Reeves conjugate gradient1 + Armijo backtracking line search2 | No — cg_dt stays 0, only the iteration counter moves |
config_fire.msp |
FIRE 2.0 (Bitzek et al. 20063 / Guénolé et al. 20204) — inertial pseudo-dynamics | Yes — adaptive fire_dt |
Conjugate Gradient¶
This is a purely static minimization, using the Fletcher-Reeves conjugate-gradient method1 with an Armijo backtracking line search2: vx/vy/vz are repurposed to hold the CG search direction \(\mathbf{h}\) rather than a real velocity, and cg_dt is fixed at 0.
Warning
Because vx/vy/vz hold the search direction, not velocity, don't resume a time-integrated scheme (verlet_nve, …) directly from a restart file written during/after a CG run without first re-initializing velocities (init_temperature/scale_temperature) — otherwise the leftover direction vector would be read back in as spurious atomic velocities.
Conjugate-gradient equations (Fletcher-Reeves1 + Armijo backtracking2)
Fletcher-Reeves coefficient, forced to 0 (a plain steepest-descent restart) on the first iteration or every cg_restart_period iterations — plain Fletcher-Reeves directions degrade without a periodic restart:
Search direction, updated in place from the new force:
Trial step, scaled so the largest single-atom displacement equals the current step size \(\alpha\):
Armijo sufficient-decrease bound — a trial is accepted when its energy falls at or below this:
includes:
- config_conjugate_gradient.msp
global:
cg_tolerance: <float>
alpha: <length>
alpha_min: <length>
alpha_max: <length>
alpha_shrink_factor: <float>
alpha_grow_factor: <float>
c1: <float>
cg_restart_period: <int>
cg_dt: <time>
cg_tolerance: float, default 1.0e-4 # Convergence threshold on max atomic force, in eV/Å (compared against an eV-converted copy of force_max, not the raw force/mass value used internally).
alpha: length, default 0.01 ang # Current line-search step size — the largest single-atom displacement a trial makes; adapts every trial.
alpha_min: length, default 1.0e-7 ang # Give up the line search (and the whole CG run) once alpha shrinks below this.
alpha_max: length, default 1.0 ang # Cap on step growth after repeated accepted trials.
alpha_shrink_factor: float, default 0.5 # alpha *= this on a rejected trial.
alpha_grow_factor: float, default 1.2 # alpha *= this on an accepted trial.
c1: float, default 1.0e-4 # Armijo sufficient-decrease constant.
cg_restart_period: int, default 20 # Force a steepest-descent restart (beta = 0) every N outer iterations.
cg_dt: time, default 0.0 ps # Fed to next_time_step so physical_time never advances; the timestep counter still increments (restart/snapshot/screen triggers keep working).
includes:
- config_conjugate_gradient.msp
global:
cg_tolerance: 1.0e-5
alpha: 0.02 ang
alpha_max: 0.2 ang
cg_trajectory_loop (exaStamp/data/config/config_conjugate_gradient.msp) expands to:
cg_trajectory_loop:
loop: true
condition: cg_loop_continue
body:
- begin_iteration
- simulation_thermodynamic_state
- cg_force_stats
- cg_beta_step:
rebind: { restart_period: cg_restart_period }
body: [ cg_beta ]
- cg_direction
- cg_snapshot_ref_energy:
rebind: { value: ref_energy }
body: [ thermo_potential_energy ]
- backup_r_lt
- cg_line_search_loop
- check_and_update_particles
- end_iteration
- next_time_step: { rebind: { dt: cg_dt } }
cg_force_stats(exaStamp/src/numerical_schemes/cg_force_stats.cpp) reduces the per-particle force intoforce_max(convergence test) andforce_sqnorm(Fletcher-Reeves numerator/denominator) — a fully generic force reduction despite thecg_prefix, also reused by FIRE below.cg_beta_stepbatchescg_beta, rebinding itsrestart_periodinput tocg_restart_period— computes \(\beta\) and tracks the iteration count since the last restart.cg_direction(exaStamp/src/numerical_schemes/cg_direction.cpp) updates \(\mathbf{h}\) in place and reduces the two scalars the line search needs:dir_max(\(\max|\mathbf{h}|\)) andslope.backup_r_ltcheckpoints the current positions before the line search starts trying steps — paired withrestore_r_ltinside the search below, which rolls back to this checkpoint on a rejected trial.cg_line_search_loopis the nested backtracking search, detailed next.- The outer loop keeps going while three conditions all hold: the force hasn't converged yet, the line search didn't bottom out at
alpha_min, andsim_continue's own iteration/wall-clock limit hasn't been hit.
cg_line_search_loop expands to:
cg_line_search_loop:
loop: true
condition: cg_continue_search
body:
- cg_trial_step
- ghost_update_r
- compute_all_forces_energy
- simulation_thermodynamic_state
- cg_snapshot_trial_energy:
rebind: { value: trial_energy }
body: [ thermo_potential_energy ]
- cg_armijo_bound
- cg_check_armijo:
rebind: { value: trial_energy, threshold: bound, result: reject_step }
body: [ greater_than ]
- cg_restore_if_rejected:
condition: reject_step
body: [ restore_r_lt ]
- cg_check_alpha_floor:
rebind: { value: alpha, threshold: alpha_min, result: line_search_ok }
body: [ greater_than ]
- cg_shrink_if_rejected:
condition: reject_step
body:
- cg_alpha_shrink:
rebind: { value: alpha, factor: alpha_shrink_factor }
body: [ cg_value_scale ]
- cg_grow_if_accepted:
condition: not reject_step
body:
- cg_alpha_grow:
rebind: { value: alpha, factor: alpha_grow_factor, max: alpha_max }
body: [ cg_value_scale ]
cg_trial_step(exaStamp/src/numerical_schemes/cg_trial_step.cpp) moves every particle by \(H^{-1}\mathbf{h}\,(\alpha/d_{max})\), where \(d_{max}\) iscg_direction'sdir_maxoutput — a cheap trial, followed only by a ghost-position update and a force/energy recompute (a full neighbor-list rebuild only happens once per outer iteration, after a trial is finally accepted).cg_armijo_bound(exaStamp/src/numerical_schemes/cg_armijo_bound.cpp) computes \(E_{bound}\); the trial is rejected (reject_step = true) whenever its energy exceeds it.- On rejection,
restore_r_ltundoes the trial andcg_alpha_shrink(an instance ofcg_value_scale, the small generic clamped-rescale operator shared by both shrink/grow directions) shrinksalpha; on acceptance, positions are kept as-is andcg_alpha_growgrowsalphaback up, capped atalpha_max. The search itself stops once a trial is accepted oralphabottoms out belowalpha_min. - The source currently also prints per-trial
alpha/rejectdebug output inside this loop, flagged in its own comment as temporary and likely to be removed.
FIRE 2.0¶
FIRE performs genuine MD-with-inertia: unlike CG, vx/vy/vz hold a real velocity, integrated with ordinary velocity-Verlet, biased at each step towards the force direction. fire_dt is itself adaptive and is what actually advances physical_time. The algorithm implemented here is specifically FIRE 2.0: Bitzek et al.3 introduced the original method, and Guénolé et al.4 later revised its adaptation rules (the startup grace period, and shrinking alpha on a downhill step rather than only resetting it on an uphill one) into the FIRE 2.0 variant this scheme actually follows.
FIRE 2.0 equations (Bitzek et al. 20063 / Guénolé et al. 20204)
Power — the steering signal driving the whole adaptation:
If \(P>0\) (still going downhill): after more than fire_delaystep consecutive downhill steps, grow the timestep by \(g_{dt}\) (fire_dt_grow), capped at \(dt_{max}\) (fire_dt_start \(\times\) fire_dt_max_factor), and shrink the mixing coefficient by \(s_{\alpha}\) (fire_alpha_shrink):
If \(P\le0\) (uphill/stationary): undo the last half-drift, zero the velocity, and — unless still inside the startup grace period — shrink the timestep by \(s_{dt}\) (fire_dt_shrink), floored at \(dt_{min}\) (fire_dt_start \(\times\) fire_dt_min_factor), and reset the mixing coefficient to \(\alpha_{start}\) (fire_alpha_start):
Velocity mixing (Euler discretization of the bias term in the FIRE equation of motion), applied every step regardless of uphill/downhill:
includes:
- config_fire.msp
global:
fire_tolerance: <float>
fire_dt_start: <time>
fire_dt: <time>
fire_dt_max_factor: <float>
fire_dt_min_factor: <float>
fire_delaystep: <int>
fire_dt_grow: <float>
fire_dt_shrink: <float>
fire_alpha_start: <float>
fire_alpha: <float>
fire_alpha_shrink: <float>
fire_pneg_max: <int>
fire_initial_delay: <bool>
fire_tolerance: float, default 1.0e-4 # Convergence threshold on max atomic force, in eV/Å (same eV-conversion caveat as cg_tolerance).
fire_dt_start: time, default 1.0e-3 ps # Reference timestep dt_max/dt_min are scaled from — same value an MD run at low temperature would use.
fire_dt: time, default 1.0e-3 ps # Current adaptive timestep; seeded to fire_dt_start.
fire_dt_max_factor: float, default 10.0 # tmax: dt_max = fire_dt_start * this.
fire_dt_min_factor: float, default 0.02 # tmin: dt_min = fire_dt_start * this.
fire_delaystep: int, default 20 # N_delay: consecutive downhill steps needed before dt grows again; also gates the startup grace period before the first shrink.
fire_dt_grow: float, default 1.1 # dt *= this after delaystep consecutive downhill steps.
fire_dt_shrink: float, default 0.5 # dt *= this on an uphill/stationary step.
fire_alpha_start: float, default 0.25 # alpha0: mixing coefficient right after a reset.
fire_alpha: float, default 0.25 # Current adaptive mixing coefficient; seeded to fire_alpha_start.
fire_alpha_shrink: float, default 0.99 # alpha *= this after delaystep consecutive downhill steps.
fire_pneg_max: int, default 2000 # vdfmax: give up after this many consecutive uphill steps.
fire_initial_delay: bool, default true # Skip the very first dt-shrinks during the startup grace period.
Defaults mirror FIRE 2.0's published/LAMMPS defaults (Guénolé et al. 2020, Table 1).
includes:
- config_fire.msp
global:
fire_tolerance: 1.0e-5
fire_dt_start: 1.0e-3 ps
fire_dt: 1.0e-3 ps
fire_trajectory_loop (exaStamp/data/config/config_fire.msp) expands to:
fire_trajectory_loop:
loop: true
condition: fire_loop_continue
body:
- begin_iteration
- simulation_thermodynamic_state
- fire_power
- fire_adapt_step:
rebind:
dt_start: fire_dt_start
dt_max_factor: fire_dt_max_factor
dt_min_factor: fire_dt_min_factor
delaystep: fire_delaystep
dt_grow: fire_dt_grow
dt_shrink: fire_dt_shrink
alpha_start: fire_alpha_start
alpha_shrink: fire_alpha_shrink
pneg_max: fire_pneg_max
initial_delay: fire_initial_delay
dt: fire_dt
alpha: fire_alpha
body: [ fire_adapt ]
- fire_uphill_correct:
condition: fire_uphill
body:
- fire_halfstep_back:
rebind: { dt: fire_dt }
body: [ push_v_r: { dt_scale: -0.5, xform_mode: INV_XFORM } ]
- fire_zero_velocity
- fire_kick1:
rebind: { dt: fire_dt }
body: [ push_f_v: { dt_scale: 0.5 } ]
- fire_norms
- fire_velocity_mix_step:
rebind: { alpha: fire_alpha }
body: [ fire_velocity_mix ]
- fire_drift:
rebind: { dt: fire_dt }
body: [ push_v_r: { dt_scale: 1.0, xform_mode: INV_XFORM } ]
- check_and_update_particles
- compute_all_forces_energy
- fire_kick2:
rebind: { dt: fire_dt }
body: [ push_f_v: { dt_scale: 0.5 } ]
- cg_force_stats
- end_iteration
- next_time_step: { rebind: { dt: fire_dt } }
fire_power(exaStamp/src/numerical_schemes/fire_power.cpp) reduces \(P=\mathbf{F}\cdot\mathbf{v}\) over all atoms, feedingfire_adapt's uphill/downhill test.fire_adapt(exaStamp/src/numerical_schemes/fire_adapt.cpp) is the whole dt/alpha state machine above; besidesdt/alphait also tracks the consecutive downhill/uphill counters and reportsfire_uphillandfire_not_stalled(goes false oncefire_pneg_maxconsecutive uphill steps have piled up without a single downhill step, feeding into the outer loop's own stopping condition).- On an uphill step,
fire_halfstep_back(push_v_rwith a negativedt_scale) undoes the half-drift the previous iteration already applied, thenfire_zero_velocityclears \(\mathbf{v}\) — order matters, the correction needs the still-nonzero velocity before it's cleared. fire_kick1/fire_drift/fire_kick2are the ordinary velocity-Verlet half-kick/drift/half-kick, reusing the same generic push operators asverlet_nve, just rebound to the adaptivefire_dtinstead of the simulation's fixeddt.fire_norms(exaStamp/src/numerical_schemes/fire_norms.cpp) reduces \(|\mathbf{F}|^2\)/\(|\mathbf{v}|^2\) right after the first half-kick (so \(\mathbf{F}\) is still the pre-step force, \(\mathbf{v}\) is already at \(t+\Delta t/2\)), feedingfire_velocity_mix's \(|\mathbf{v}|/|\mathbf{F}|\) ratio.cg_force_statsis reused as-is for the end-of-stepforce_maxconvergence test/progress display — the same fully generic reduction CG uses above.
fire_first_iteration sets \(\mathbf{v}(0)=0\) via fire_zero_velocity before the first progress print — the only difference from CG's first-iteration setup besides the scheme swap itself.
-
Fletcher, Reeves, The Computer Journal, 7, 149-154 (1964) ↩↩↩
-
Nocedal, Wright, Numerical Optimization, Springer Series in Operations Research (1999) ↩↩↩
-
Bitzek, Koskinen, Gahler, Moseler, Gumbsch, Phys Rev Lett, 97, 17021 (2006) ↩↩↩
-
Guénolé, Nöhring, Vaid, Houllé, Xie, Prakash, Bitzek, Comput Mater Sci, 175, 109584 (2020) ↩↩↩