Skip to content

Batched evaluation (vmap over geometries)

When the atoms and basis are fixed and only the coordinates vary (a conformer set, a bond scan, an ML dataset), run_{rks,uks,ks}_batched evaluate the whole batch in one vmapped call. Only centers = coords[atom_index] changes per geometry, on a shared basis template; the Becke grid moves with the nuclei and the SCF runs on-device.

import jax; jax.config.update("jax_enable_x64", True)
import jax.numpy as jnp
import numpy as np
from dftax import run_rks_batched
from dftax.system import Molecule
from dftax.energy.xc import PBE

# H2 bond scan: 8 geometries differing only in the bond length
mol = Molecule.from_xyz("H 0 0 0; H 0 0 1.4", "sto-3g")
lengths = jnp.linspace(1.0, 2.4, 8)                       # Bohr
coords = jnp.stack([jnp.array([[0., 0., 0.], [0., 0., L]]) for L in lengths])

res = run_rks_batched(mol, coords, PBE(), forces=True)
print(np.asarray(res.converged))                         # all True
print(np.asarray(res.e_tot))                             # (8,) energies
print(res.forces.shape)                                  # (8, 2, 3) Ha/Bohr

BatchedResult carries e_tot, converged, n_iter, and (with forces=True) forces, each batched on the leading axis. run_uks_batched handles open shells; run_ks_batched dispatches by spin.

Notes. Forces are not taken by differentiating through the SCF (the while_loop solve isn't reverse-differentiable). Instead they reuse the analytic Pulay-free force kernel inside the same vmap. A fixed-shape Löwdin orthonormalizer keeps array shapes uniform across the batch, which assumes a well-conditioned basis (the conformer-dataset regime). v1 targets the exact path (small/moderate bases); batched DF is a follow-up.

Validated against the serial path: batched energies match per-geometry run_rks and batched forces match rks_forces to ≤1e-9 (the tolerance the test suite enforces).