Skip to content

feat(xtsci): run Guenole fast inertial relaxation engine 2.0 for fire2 - #871

Merged
HaoZeke merged 2 commits into
TheochemUI:developfrom
HaoZeke:feat/fire2-optin
Nov 5, 2026
Merged

HaoZeke merged 2 commits into
TheochemUI:developfrom
HaoZeke:feat/fire2-optin

Conversation

@HaoZeke

@HaoZeke HaoZeke commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

[Xtsci] method = fire2 now runs version 2.0 of the fast inertial relaxation engine (FIRE) as algorithm 2 of Guenole et al. (2020) gives it, with the parameters of Table 1 in that paper.
Until now the token ran the rgmin default schedule for RGMIN_FIRE2.
That schedule keeps the FIRE 1.0 parameters and has no half-step correction, no initial delay and no dt_min, so a user who asked for FIRE 2.0 got the old parameters.
method = fire, opt_backend = rgmin and the native FIRE keep the 1.0 schedule.

The first commit fixes the time step of the whole-run path.
With eindir-core linked, XtsciOptimizer::run passed max_move to rgmin_minimize_eindir as istep.
FIRE and quick-min read istep as their time step, so time_step had no effect there.
Both paths now take istep from one helper: fire, fire2 and quickmin get time_step, and the other methods still get max_move.

Example

Relax the displaced 13-atom Lennard-Jones (LJ) cluster in client/unit_tests/data/systems/lj13_saddle/displacement.con, copied to pos.con, with this config.ini:

# snapper:off
[Main]
job = minimization

[Potential]
potential = lj

[Optimizer]
opt_method = xtsci
converged_force = 0.001

[Xtsci]
method = fire2
# snapper:on

eonclient prints Minimization converged within tolerence and Final Energy: -39.96535193391027.
In results.dat, the run records 108 total_force_calls, fire2 xtsci_method and xtsci optimizer_backend.

The client refuses fire2 when rgmin is older than application binary interface (ABI) minor 27, and does not fall back to the old schedule.

Before and after

Force calls of single eonclient minimizations with the config above, converged_force = 0.001, the default time_step and max_move, and rgmin linked without eindir-core.
"Before" is a client built without this pull request, and "after" is a client built with it.
Both clients link the same rgmin build.
The inputs come from client/unit_tests/data/systems.

  • LJ13: lj13_saddle/displacement.con with potential = lj.
  • Pt heptamer on a frozen Pt slab: Pt_Heptamer_FrozenLayers/displacement.con with potential = morse_pt.
  • Cu: cu_fcc/pos.con with potential = emt.
System Potential Native FIRE rgmin fire fire2 before fire2 after
LJ13 lj 138 138 130 108
Pt heptamer morse_pt 84 84 85 40
Cu emt 47 47 43 9

The counts for native FIRE and rgmin fire stay the same.
We ran on 8 virtual CPUs of an Intel Xeon processor, Linux 6.12 on x86_64, with gcc 13.3.0 and --buildtype release.
One run per system cannot rank the two schedules.
The hierarchical model of Goswami (2025) does that from repeated starts.

Changes

  • client/XtsciOptimizer.cpp: one helper sets istep for both step() and run.
    For the fire2 method, the solver setup calls rgmin_solver_set_fire_variant with RGMIN_FIRE_GUENOLE2020 after it creates the session.
    rgmin_minimize_eindir takes a method and no variant, so run keeps fire2 on the step-wise session loop, and fire2 follows the paper in both step() and run.
  • client/unit_tests/XtsciFireTest.cpp: the new test_xtsci_fire, built only when rgmin is linked.
  • The eon-schema model, xtsci.json, the glossary and the optimizer guide now describe what fire2 runs, and two newsfragments cover the two commits.

Tests

test_xtsci_fire replays algorithm 2 from the paper on a quadratic with stiffnesses 1, 3 and 7.
Over 60 steps the replay moves half a step back five times inside the delay and three times after it.
The largest gap between the fire2 trajectory and the replay is 1e-17, and FIRE 1.0 strays from it by 0.65.
Three more cases cover the rest.
For fire and fire2, run follows time_step and ignores max_move.
For fire2, run and a loop of step() calls reach the same point.
fire and fire2 relax a displaced LJ13 to the same minimum along different paths.

We ran both suites, and every test passed:

  • meson test --suite eon: 75 of 75 OK with rgmin only, and 75 of 75 OK with eindir-core linked.
    test_xtsci_fire, test_rgmin_backend, test_xtsci_eindir, test_optimizer and test_minimization pass in both builds.
  • pytest packages/eon-schema: 57 passed.

To reproduce, inside the pixi dev-lite environment:

# snapper:off
# rgmin C ABI at the revision that .github/workflows/ci_rg_backends.yml pins
PREFIX="$HOME/rgprefix"
RGMIN_REV=$(awk '/RGMIN_REV:/{print $2}' eOn/.github/workflows/ci_rg_backends.yml)
cargo install cargo-c --version 0.10.23 --locked
git clone https://github.com/OmniPotentRPC/rgmin
git -C rgmin checkout --detach "$RGMIN_REV"
cd rgmin
cargo cinstall --release --features capi --prefix="$PREFIX" --libdir="$PREFIX/lib"
cp include/rgmin.h "$PREFIX/include/"
cp -r include/dlpack "$PREFIX/include/"
cd ../eOn
export PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig"
export LD_LIBRARY_PATH="$PREFIX/lib"
meson setup bbdir --buildtype release -Dwith_rgmin=enabled
meson compile -C bbdir
meson test -C bbdir --suite eon --print-errorlogs
pytest packages/eon-schema
# the trajectory gaps and half-step counts above
(cd client/unit_tests/data/systems/neb_morse && ../../../../../bbdir/client/test_xtsci_fire -s)
# snapper:on

Build note

No build with eindir-core linked compiles against a published eindir-core.
The published 0.6.0 crate lacks the objective descriptor that the eOn adapter in XtsciEindir.cpp uses.
Under C++, its generated header puts every name in namespace eindir, while rgmin.h and eOn use the names unqualified.
We built eindir from its main branch for the runs with eindir-core linked.
For those runs, a local eindir-core.h includes the generated header and adds using namespace eindir.
That header is not part of this pull request.
The continuous integration (CI) job builds without eindir-core, so run there already used the session loop.

Left for later

  • FIRE 2.0 in eOn's own client/FIRE.cpp.
  • FIRE 2.0 for the rgsaddle band and min-mode, which build their FIRE without a variant.
  • The eindir-core header and descriptor mismatch above.
  • A step cap on the whole-run path, where step() caps each atom at max_move.

References

The first reference appears in a journal of the American Institute of Physics (AIP).
The second names the Large-scale Atomic/Molecular Massively Parallel Simulator (LAMMPS).

  • R. Goswami, Bayesian hierarchical models for quantitative estimates for performance metrics applied to saddle search algorithms, AIP Advances 15, 085210 (2025), doi:10.1063/5.0283639.
  • J. Guenole, W. G. Nohring, A. Vaid, F. Houlle, Z. Xie, A. Prakash, E. Bitzek, Assessment and optimization of the fast inertial relaxation engine (FIRE) for energy minimization in atomistic simulations and its implementation in LAMMPS, Comput. Mater. Sci. 175, 109584 (2020), doi:10.1016/j.commatsci.2020.109584.
  • E. Bitzek, P. Koskinen, F. Gahler, M. Moseler, P. Gumbsch, Structural relaxation made simple, Phys. Rev. Lett. 97, 170201 (2006), doi:10.1103/PhysRevLett.97.170201.

With eindir-core linked, XtsciOptimizer::run sends a whole minimization through rgmin_minimize_eindir.
That call passed max_move as rgmin_control_t::istep, which the fast inertial relaxation engine (FIRE) and quick-min read as their time step.
The session path in step() already passed time_step.
On the base, the new test showed it on a quadratic.
Time steps of 0.1 and 0.2 ended at the same point, while max_move values of 5 and 50 diverged.
Both paths now take istep from one initial_step helper.
FIRE, its 2.0 variant and quick-min get time_step.
The other methods still start from max_move.
The fire2 token mapped to RGMIN_FIRE2.
By default rgmin runs that method with the fast inertial relaxation engine (FIRE) 1.0 parameters.
That schedule has no half-step correction, no initial delay and no dt_min.
ensureSolver now calls rgmin_solver_set_fire_variant with RGMIN_FIRE_GUENOLE2020 right after rgmin_solver_create.
fire2 then runs algorithm 2 of Guenole et al. (2020) with semi-implicit Euler and the parameters of Table 1 in that paper.
The variant takes dt0 from control.istep, set from time_step.
rgmin_minimize_eindir takes no variant, so Optimizer::run keeps fire2 on the session loop.
A client built against rgmin older than application binary interface (ABI) minor 27 refuses fire2 instead of running the old schedule.
The fire token and the native FIRE keep the 1.0 schedule.

A new test replays algorithm 2 from the paper on a quadratic with three stiffnesses.
Over 60 steps it moves half a step back five times inside the delay and three times after it.
The fire2 trajectory matches the replay to 1e-17.
FIRE 1.0 strays by 0.65, and fire2 without the variant by 1.01.
Another test checks that run gives the same end point as a loop of step calls.
The docs now describe what fire2 runs in the schema model, the published schema, the glossary and the optimizer guide.
@codecov

codecov Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@HaoZeke
HaoZeke merged commit 0bb29da into TheochemUI:develop Nov 5, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant