nvalchemiops.jax.lbfgs: Geometry Optimization#
JAX bindings for the batched L-BFGS geometry optimizer.
JAX bindings for the batched L-BFGS geometry optimizer.
L-BFGS reaches a given force tolerance in far fewer force evaluations than the FIRE optimizers – the cost that dominates relaxation with a machine-learned potential.
JAX arrays are immutable, so unlike the PyTorch binding these entry points
return a new state. Both
LBFGSState and
LBFGSCellState are registered
pytrees, so a state crosses jax.jit as one argument and one
donate_argnums entry donates every field:
state = lbfgs_prepare_state(num_atoms, num_systems)
@functools.partial(jax.jit, donate_argnums=(0, 1))
def relax_step(positions, state, forces):
return lbfgs_step_coord(positions, forces, state, batch_idx)
for _ in range(max_steps):
forces = model(positions)
if float(jnp.linalg.norm(forces, axis=1).max()) < force_tol:
break
positions, state = relax_step(positions, state, forces)
Test before stepping, as above: the returned positions are a new,
unevaluated point, and the forces you passed describe the point before it
(kept in x_base/force_base). The optimizer owns no tolerance and has
no terminal status, matching FIRE2. Reading a norm back costs a host
synchronization, so test every few steps if that matters.
These operations are not differentiable: jax.grad through a step fails
rather than returning a silently wrong answer.
State#
lbfgs_prepare_state() allocates, initializes and validates the whole
state; in a functional setting resetting and allocating are the same thing, so
calling it again is how you restart. The arrays stay yours – the state is a
plain dataclass with a validate() method; see
nvalchemiops.dynamics.optimizers.lbfgs for shapes and required initial
contents.
Every array follows the coordinate dtype, so an fp32 state is fp32 end to end
and needs no JAX_ENABLE_X64.
Donation and pointer stability#
Every mutable array is an input-output alias, so XLA may reuse each input buffer for the matching output. Both consequences are about performance:
Donate the state. Without donation JAX copies, doubling peak memory and moving the pointers. On the variable-cell path donate
statebut notcell_state: its five chart fields are read-only and come back unchanged, so XLA cannot reuse their buffers.Keep topology out of the donated set.
batch_idxnever changes, so close over it.
Under JaxCallableGraphMode.WARP the step replays as a CUDA graph. The
capture is keyed on input addresses, so a fresh forces array each step
gives a small working set rather than one graph per call; measurements settle
at four or five. If the count grows without bound, pass
graph_mode="warp_staged", which keys on the call instead at the cost of
one copy per staged array.
Scalars are baked into the compiled call, so changing maxstep between
steps triggers a recompilation.
See also
nvalchemiops.dynamics.optimizers.lbfgsthe Warp implementation, which documents the algorithm, sign convention and precision policy.
Tip
JAX arrays are immutable, so these entry points take the state and return
a new one rather than writing in place. Both state classes are registered
pytrees, so a state passes through jax.jit as one argument and
donate_argnums donates every field at once. See the module
documentation above for the donation and CUDA-graph contract.
Coordinate Relaxation#
- nvalchemiops.jax.lbfgs.lbfgs_prepare_state(num_dofs, num_systems, *, dtype=jnp.float64, history_size=6)[source]#
Allocate, initialize and validate a complete optimizer state.
Call this once, before the first step. The three fields that do not start at zero are set for you –
alpha_stepto one,iterationto minus one. Calling it again is how you reset.LBFGSStateis a registered pytree, so the whole thing can be passed throughjax.jit, donated, and carried bylax.while_loop. You are not obliged to use this function: build one from arrays you own and callLBFGSState.validate()yourself.- Parameters:
num_dofs (int) – Degrees of freedom the optimizer moves. On the variable-cell path this is
num_atoms + 2 * num_systems, not the atom count.num_systems (int) – Independent systems in the batch.
dtype (optional) – Coordinate precision, float32 or float64. Every per-system scalar follows it, so this selects an end-to-end fp32 or fp64 state – and an fp32 state needs no
JAX_ENABLE_X64.history_size (int, optional) – Stored curvature pairs
m; 3 to 7 is the usual range.
- Return type:
- nvalchemiops.jax.lbfgs.lbfgs_step_coord(positions, forces, state, batch_idx, *, maxstep=0.2, curvature_eps=None, graph_mode='warp')[source]#
Advance one batched L-BFGS step, consuming one force evaluation.
Returns new arrays; nothing is mutated in place. Donate
positionsandstateso XLA can reuse their memory.- Parameters:
positions (jax.Array, shape (num_atoms, 3)) – Current geometry.
forces (jax.Array, shape (num_atoms, 3)) – Forces at
positions. Forces, not gradients.state (LBFGSState) – From
lbfgs_prepare_state(), or built from your own arrays.batch_idx (jax.Array, shape (num_atoms,), dtype int32) – Sorted system index per atom. Static topology, so close over it.
maxstep (float, optional) – Largest distance an atom may move in one step. Zero disables the trust region.
curvature_eps (float, optional) – Relative threshold below which a curvature pair is discarded. Defaults to
1e-6for float32 coordinates and1e-10for float64:ysis accumulated at the coordinate precision, so one value cannot serve both.graph_mode ({"warp", "warp_staged", "none"}, optional) – How the step is captured.
"warp"replays a CUDA graph and is the default;"warp_staged"keys the capture on the call rather than on buffer addresses, which helps if the capture count grows without bound;"none"disables capture.
- Returns:
positions (jax.Array) – The advanced geometry.
state (LBFGSState) – The advanced state. The input state is unchanged.
- Raises:
ValueError – If this call’s inputs are incompatible with the state.
- Return type:
Variable-Cell Relaxation#
Coordinates and cell are mapped into one packed coordinate vector, so the
two-loop recursion couples them automatically. Build ext_atom_ptr and
ext_batch_idx with
extend_atom_ptr() and
atom_ptr_to_batch_idx(), which handle ragged
batches as well as uniform ones.
- nvalchemiops.jax.lbfgs.lbfgs_prepare_cell_state(atom_ptr, cell, *, cell_force_scale=1.0, dtype=jnp.float64, alignment_atol=None)[source]#
Build a complete, ready-to-step variable-cell chart from atom topology.
One call: give it the ordinary
atom_ptrand aligned cells, and it derives the packed topology, captures the reference chart and computeskappa. Alignment is checked before the reference cell is captured.Ragged batches are unaffected –
atom_ptralready carries each system’s atom count, so nothing here assumes an even split.- Parameters:
atom_ptr (jax.Array, shape (num_systems + 1,), dtype int32) – CSR-style atom pointer for the batch.
cell (jax.Array, shape (num_systems, 3, 3)) – Reference lattice per system, aligned.
cell_force_scale (float, optional) – Multiplier on the atom count in
kappa.dtype (optional) – Coordinate precision; must match the coordinates and the state.
alignment_atol (float, optional) – Absolute tolerance for entries strictly above the cell diagonal. Defaults to
1e-4for float32 cells and1e-8for float64 cells.
- Return type:
See also
nvalchemiops.dynamics.optimizers.lbfgsthe variable-cell contract, including the requirement that the cells be aligned first.
- nvalchemiops.jax.lbfgs.lbfgs_step_coord_cell(positions, cell, forces, stress, state, cell_state, batch_idx, *, maxstep=0.2, curvature_eps=None, graph_mode='warp')[source]#
Advance one variable-cell step, relaxing coordinates and cell together.
Returns new arrays; nothing is mutated. Donate everything that comes back so XLA can reuse the memory.
statemust be sized fornum_atoms + 2 * num_systemsdegrees of freedom, since the cell contributes two entries per system. Fillcell_statefrom_lbfgs_set_reference_cell()and_lbfgs_cell_kappa()once before the first step.- Parameters:
cell (jax.Array, shape (num_systems, 3, 3)) – Lattice vectors as columns, aligned by
align_cellbefore the first step. See the variable-cell contract innvalchemiops.dynamics.optimizers.lbfgs, which is the single authoritative statement of the cell rules.stress (jax.Array, shape (num_systems, 3, 3)) – Cauchy stress, which drives the cell degrees of freedom.
state (LBFGSState, LBFGSCellState) – From the preparation functions, or built from your own arrays.
cell_state (LBFGSState, LBFGSCellState) – From the preparation functions, or built from your own arrays.
positions (Array)
forces (Array)
batch_idx (Array)
maxstep (float)
curvature_eps (float | None)
graph_mode (str)
- Returns:
positions, cell (jax.Array) – The advanced geometry and lattice.
state, cell_state (LBFGSState, LBFGSCellState) – The advanced states. The chart fields of
cell_statecome back unchanged; only its scratch fields are rewritten.
- Return type:
See also
_lbfgs_set_reference_cellmust be called first.
_lbfgs_cell_kappamust be called first.
lbfgs_step_coordthe coordinate-only equivalent.