C and C++ ABI#

Solvers live in Rust. Geometry hosts that call one outer iteration at a time hold an opaque rgmin_solver_t; scripts that want a one-shot solve still call rgmin_minimize. Every vector is a dlpk DLManagedTensorVersioned tensor.

Session (eOn / ASE step)#

#include "xts.h"

rgmin_control_t ctrl = { .maxiter = 200, .gtol = 1e-8, .istep = 1.0, .memory = 8 };
rgmin_solver_t *s = rgmin_solver_create(RGMIN_LBFGS, &ctrl, n);
rgmin_report_t report;
while (!done) {
    rgmin_solver_set_maxmove(s, max_move);
    rgmin_solver_set_qn_step(s, RGMIN_QN_NEWTON); /* or RGMIN_QN_LBFGS + P as H0 */
    rgmin_solver_set_accept(s, RGMIN_ACCEPT_NONE);
    rgmin_solver_step_hess_fg(s, evalgrad, hess, user, x, &report);
}
rgmin_solver_free(s);

rgmin_precon_init and rgmin_solver_set_precon install Exp, Lindh, the bonded metric, or the automatic choice. NaN mu estimates the energy scale on the next step. L-BFGS uses the matrix as H0. FIRE and nonlinear CG use it as a metric. rgmin_solver_clear_precon removes it. Morse bonds and FF torsions are Rust-only.

rgmin_solver_forget drops pairs / conjugacy / moments. Newton and RFO use rgmin_solver_step_hess_fg (fused energy and gradient, one host potential call). Split eval / grad callbacks remain. accept is none (take the clipped step), energy, or nonmonotone. Callbacks are arguments of each step, not stored on the handle.

A Cartesian session starts with a per-atom cap of 0.2. rgmin_solver_set_atom_maxmove replaces it. A non-positive argument clears it. A positive maxmove on rgmin_control_t, with no call to that setter, is the Euclidean step limit and leaves the per-atom default off. The cap is the largest per-atom displacement. The C++ OptimizeControl.maxmove is 0, so optimize keeps the per-atom default. ASE Optimizer.defaults maxstep is 0.2 at tag 3.29.0 (https://gitlab.com/ase/ase/-/blob/3.29.0/ase/optimize/optimize.py). eOn max_move is 0.2 Å (https://henkelmanlab.org/eon/optimizer.html); the schema field OptimizerOptions.maxMove is 0.2 at 4be157d4 (https://github.com/TheochemUI/eOn/blob/4be157d43265af6b1140aaac0e52e42add62693f/schema/eon_params.capnp).

One-shot#

#include "xts.h"

rgmin_control_t ctrl = { .maxiter = 200, .gtol = 1e-8, .istep = 1.0, .memory = 8 };
rgmin_report_t report;
rgmin_status_t st = rgmin_minimize(eval, grad, user, x, &ctrl, RGMIN_LBFGS, &report);

A non-CPU tensor returns RGMIN_UNSUPPORTED_DEVICE. The ABI does not change when a CUDA path lands.

C++#

#include <xts/optimize.hpp>

xts::optimize::Control ctrl;
auto report = xts::optimize::minimize(eval, grad, user, x, ctrl,
                                      xts::optimize::Method::Lbfgs);

xtensor callers include xts/xtensor.hpp, which adapts xt::xarray to the same rgmin_minimize entry. There is no solver in the header.

Manifold#

rgmin_solver_set_manifold(s, RGMIN_MANIFOLD_SPHERE);

Tokens: RGMIN_MANIFOLD_EUCLIDEAN (default), RIGID_QUOTIENT (R^{3N}/SE(3), Sella Cartesian), MW_RIGID (Page–McIver / Sella IRC Eckart), SPHERE, SO3 (length 9), STIEFEL (St(n,1)), SE3 (length 12). See retract onto an embedded manifold. rgmin_solver_set_masses supplies N atomic masses for MW_RIGID.