Models#
Every model takes an AtomicGraph and
returns node_energy and energy;
ForceStressOutput adds forces and stress
by autograd. Models are built by name from a config, or constructed
directly with the same keys:
from xnn.common.models import available_models, build_model, ForceStressOutput
available_models() # ['aimnet2', 'allegro', 'ani', 'bamboo', 'cace', 'cnn3d', 'dimenet', 'dimenet++', 'hdnnp', 'mace', 'nequip', 'nnp1g', 'painn', 'physnet', 'reaxff', 'schnet', 'se3cnn', 'spookynet', ...]
model = build_model(cfg.model) # dispatches to <Model>.from_config(cfg.model)
model = ForceStressOutput(build_model(cfg.model), compute_stress=True)
out = model(graph) # energy, node_energy, forces, stress
Pre-trained models load with from_pretrained()
(Load a Pre-trained Model). NequIP, MACE, Allegro and the SE(3)
steerable CNN need the gnn extra (e3nn); every other model is plain
PyTorch. The snippets below show
the options most runs set; each class documents the full list.
MACE#
MACE: higher-order equivariant message passing
(Batatia et al.), matching ACEsuit/mace.
Every published MACE-MP / MACE-OFF checkpoint loads through the hub.
model:
name: mace
cutoff: 5.0
species: [1, 6, 8]
num_channels: 64
num_interactions: 2 # any depth from 0
max_ell: 3
correlation: 3
avg_num_neighbors: 20.0
atomic_energies: {1: -13.6, 6: -1029.9, 8: -2042.8}
# foundation: mace-off23-small # start from a pretrained checkpoint instead
Also max_L, hidden_irreps, MLP_irreps, radial_type,
distance_transform (Agnesi / Soft), pair_repulsion (ZBL),
scale / shift.
NequIP#
NequIP: E(3)-equivariant message passing
with gated nonlinearities (Batzner et al.), matching mir-group/nequip; state dicts transplant directly.
model:
name: nequip
cutoff: 5.0
species: [1, 6, 8]
num_features: 32
n_layers: 3
l_max: 2
parity: true
avg_num_neighbors: 20.0
atomic_energies: {1: -13.6, 6: -1029.9, 8: -2042.8}
Also invariant_layers / invariant_neurons, resnet, use_sc,
trainable_rbf, atomic_scales.
Allegro#
Allegro: strictly local equivariant
many-body potential without message passing (Musaelian et al.), matching
mir-group/allegro v0.3.0.
model:
name: allegro
cutoff: 5.0
species: [1, 6, 8]
num_layers: 2
num_tensor_features: 16
l_max: 1
two_body_latent: [32, 64]
latent: [64]
avg_num_neighbors: 20.0
atomic_energies: {1: -13.6, 6: -1029.9, 8: -2042.8}
Also parity, env_embed, edge_eng, latent_resnet,
atomic_scales.
CACE#
CACE: the Cartesian atomic cluster expansion
(Cheng 2024), body-ordered invariants from Cartesian monomials with optional
message passing; no e3nn. Matches BingqingCheng/cace.
model:
name: cace
cutoff: 5.5
species: [1, 6, 8]
n_atom_basis: 3
max_l: 3
max_nu: 3
num_message_passing: 1 # 0 = plain Cartesian ACE
avg_num_neighbors: 10.0
Also message_types (M / Ar / Bchi), n_rbf, readout_hidden,
atomic_energies.
AIMNet2#
AIMNet2: the atoms-in-molecules network
(Anstine, Zubatyuk and Isayev 2025) for neutral, charged and open-shell
molecules; predicts partial charges exact in the net charge and adds their
Coulomb energy. Matches isayevlab/aimnetcentral; the six published families
load through the hub. Returns charges and dipole as well.
from xnn.common.models import from_pretrained
model = from_pretrained("aimnet2") # wb97m-d3, with its D3 term
model = from_pretrained("aimnet2", model_options={"coulomb": "pme"}) # periodic cells: dsf / ewald / pme
The net charge and spin multiplicity come from the structure
(total_charge, spin_multiplicity). Training from scratch uses
n_features, n_rbf, n_interactions, hidden, aim_size,
charge_channels (2 for open shells), coulomb, lr_cutoff.
SchNet#
SchNet: continuous-filter convolutions
(Schütt et al. 2017), a clean-room build of the paper with the paper’s
architecture as defaults.
model:
name: schnet
cutoff: 5.0
cutoff_fn: cosine # smooth finite cutoff; the paper trains cutoff-free
n_features: 64
n_interactions: 3
n_rbf: 50
species: [1, 6, 8]
atomic_energies: {1: -13.6, 6: -1029.9, 8: -2042.8}
Also gamma, energy_shift / energy_scale.
DimeNet and DimeNet++#
DimeNet and
DimeNetPP: directional message passing
(Gasteiger et al. 2020). Messages live on directed edges and are updated
from the distances and angles of neighboring edges through Bessel bases;
DimeNet++ swaps the bilinear interaction for a cheaper Hadamard form.
No e3nn. Matches gasteigerjo/dimenet.
model:
name: dimenet++ # or dimenet
cutoff: 5.0
n_features: 128
n_interactions: 4 # 6 for dimenet
n_rbf: 6
n_spherical: 7
species: [1, 6, 8]
atomic_energies: {1: -13.6, 6: -1029.9, 8: -2042.8}
Also n_bilinear (dimenet), n_triplet_features /
n_basis_features / n_output_features (dimenet++), p,
trainable_rbf. The reference code’s key spellings are translated.
GemNet#
GemNet: geometric message passing
(Gasteiger et al. 2021). Directed edge embeddings are updated from
neighboring edges into the same atom (distance and angle) and, with
quadruplets, from edges two hops away across an interaction edge
(adding the dihedral angle). Scaling factors replace normalization layers;
the trainer fits them on training structures before the first epoch. No
e3nn. Matches TUM-DAML/gemnet_pytorch.
model:
name: gemnet
cutoff: 5.0 # embedding cutoff
n_features: 128
n_interactions: 4
n_rbf: 6
int_cutoff: 10.0 # graphs are built to this radius
n_spherical: 7
quadruplets: true # false: GemNet-T
direct_forces: false # true: GemNet-dQ / -dT
species: [1, 6, 8]
atomic_energies: {1: -13.6, 6: -1029.9, 8: -2042.8}
Also n_edge_features, n_triplet_features / n_quadruplet_features,
n_rbf_features / n_cbf_features / n_sbf_features,
n_bilinear_triplet / n_bilinear_quadruplet, p,
forces_coupled, scaling_factors (a dict or the reference code’s JSON
file) and fit_scaling: false. Direct forces are not conservative and
cannot be combined with LES or dispersion terms or exported to TorchScript.
The reference code’s key spellings are translated.
PaiNN#
PaiNN: equivariant message passing with
scalar and vector features (Schütt et al. 2021). Optional heads predict
dipole moments from latent charges and atomic dipoles and polarizability
tensors from a rank-1 decomposition; train them with dipole_weight /
polarizability_weight on data with dipole / polarizability
labels. No e3nn. Matches the reference implementation (schnetpack).
model:
name: painn
cutoff: 5.0
n_features: 128
n_interactions: 3
n_rbf: 20 # sin(n pi r / r_cut) / r with a cosine cutoff
dipole: true # outputs dipole and latent charges
polarizability: true
species: [1, 6, 8]
atomic_energies: {1: -13.6, 6: -1029.9, 8: -2042.8}
Also radial_basis (bessel / gaussian), shared_filters,
atomic_dipoles, correct_charges, n_output_blocks, the ablations
scalar_product / vector_propagation / vector_features, and
energy_shift / energy_scale.
SE(3) steerable CNN and 3D CNN#
SteerableCNN: the SE(3)-equivariant
convolutional network of Weiler et al. (NeurIPS 2018) on voxel grids of
each atom’s environment, a clean-room build of the paper. Fields of order
0, 1 and 2 pass through gated steerable convolutions; the pooled scalar
fields are read out to per-atom energies. CNN3D
is the conventional 3D CNN baseline with the same grids, readout and block
layout. Both use VoxelGrid, one density
channel per species.
model:
name: se3cnn # or cnn3d
cutoff: 4.0 # half the side of the voxel cube
species: [1, 6, 8]
grid_size: 17 # voxels per axis
n_features: 32 # scalar fields (channels) of the last block
n_interactions: 3 # blocks
l_max: 2 # highest field order (se3cnn only)
kernel_size: 5
atomic_energies: {1: -13.6, 6: -1029.9, 8: -2042.8}
Also fields / channels (explicit block widths), strides,
activation (“ssp”), normalization (“batch”), bandlimit,
shell_width, sigma, cutoff_fn, energy_shift /
energy_scale. The steerable model is exactly invariant under the
rotations of the grid onto itself and invariant to the bandlimit under all
others; mirror images are not constrained.
Spherical CNN#
SphericalCNN (s2cnn): the
rotation-equivariant network of Cohen et al. (ICLR 2018) over spherical
signals, a clean-room build of the paper. Each atom carries one potential
channel per species sampled on a sphere around it
(SphericalGrid); ResNet blocks of
\(S^2\) and \(SO(3)\) correlations, computed by generalized FFTs,
map it to \(SO(3)\) feature maps whose invariant integral is read out
to per-atom energies. No e3nn. Matches jonas-koehler/s2cnn.
model:
name: s2cnn
cutoff: 10.0 # neighbor radius of the potential sums
species: [1, 6, 7, 8, 16]
radius: 0.48 # sphere radius (A)
bandwidth: 10 # 2b x 2b samples per sphere
n_features: 160 # channels of the last block
n_interactions: 5 # ResNet blocks (Table 3 of the paper)
atomic_energies: {1: -13.6, 6: -1029.9, 7: -1485.3, 8: -2042.8, 16: -10831.3}
Also features / bandwidths (explicit per block), exponent (1,
the paper’s potential; 2 in the reference data script), set_readout
(the paper’s DeepSet readout, e.g. [150, 100, 50]), normalization
(“batch”), activation (“relu”), n_alpha / n_beta / n_gamma
/ max_beta (filter support), cutoff_fn, energy_shift /
energy_scale. The energy is exactly invariant under rotations about the
polar axis that map the coarsest grid onto itself and invariant to the
discretization error under all others.
ANI#
ANI: per-element networks over the atomic
environment vector (Smith et al. 2017), matching aiqm/torchani. The published parameterisations are
presets, each with its training set in the data hub:
from xnn.dnn.models import ANI
ANI.ani1(species=[1, 6, 7, 8]) # ANI-1
ANI.ani1x(species=[1, 6, 7, 8]) # ANI-1x
ANI.ani1ccx(species=[1, 6, 7, 8]) # ANI-1ccx, coupled-cluster self energies
ANI.ani2x() # ANI-2x, seven elements (H, C, N, O, S, F, Cl)
model:
name: ani
preset: ani-2x # ani-1 / ani-1x / ani-1ccx / ani-2x
Without a preset: species, radial_cutoff, angular_cutoff,
hidden, activation, atomic_energies.
PhysNet#
PhysNet: message passing with explicit
electrostatics and D3 dispersion (Unke and Meuwly 2019), a pure-PyTorch
translation of MMunibas/PhysNet.
Embeds elements directly (no species list); returns charges and
dipole.
model:
name: physnet
cutoff: 10.0
n_features: 128
n_rbf: 64
n_interactions: 5
use_electrostatics: true
use_dispersion: true
HDNNP#
High-dimensional neural network potentials in the four generations of Behler’s classification (Chem. Rev. 121, 10037, 2021), matching the RuNNer code:
1G
NNP1G(nnp1g): one network maps the interatomic distances of a fixed system to its energy; optionally symmetrized over permutations of like atoms.2G
HDNNP(hdnnp): a sum of atomic energies from one network per element on atom-centered symmetry functions (Behler and Parrinello 2007).3G (
generation: 3): a second set of element networks predicts charges, shifted to the total charge, whose (Gaussian or point-charge) electrostatic energy is added (Artrith et al. 2011, Morawietz et al. 2012), optionally screened inside a cutoff.4G (
generation: 4): the networks predict electronegativities, a charge equilibration over the whole structure gives the charges (non-local charge transfer, total charge), and each charge is an extra input of the short-range networks (Ko et al. 2021).
Returns energy_short and, for 3G and 4G, charges, energy_elec
and dipole (molecules); train the charges with charge_weight on
charges labels. Options: generation, species, cutoffs,
symmetry_functions, scaling, hidden, activation,
atomic_energies, gaussian_widths, screening, hardness
(element or network), charge_neuron, runner; see
configs/model/hdnnp.yaml.
from xnn.dnn.common.runner import load_runner_model
model = load_runner_model("runner_model/") # input.nn, weights, scaling files
model.fit_scaling(train_graphs) # or fit the symmetry-function scaling
BAMBOO#
BAMBOO: a graph equivariant transformer
with a charge-equilibrium electrostatic term (Gong et al. 2024), matching
bytedance/bamboo. Works in
kcal/mol and Å; returns charges, dipole, energy_nn and
energy_elec.
model:
name: bamboo
cutoff: 5.0
n_features: 64
n_interactions: 3
num_heads: 16
use_electrostatics: true
use_dispersion: false # optional D3(CSO)
SpookyNet#
SpookyNet: message passing with
electronic degrees of freedom and nonlocal effects (Unke et al. 2021).
The total charge and the spin enter as embeddings, local interactions use
s-, p- and d-orbital-like basis functions, and self-attention over all atoms
of a structure carries nonlocal effects. A ZBL-like repulsion, damped
electrostatics of the predicted charges and D4 dispersion are added to the
energy. Returns charges and dipole as well. No e3nn. Matches the
reference implementation (OUnke/SpookyNet),
whose saved models load with SpookyNet.from_reference_checkpoint(path).
model:
name: spookynet
cutoff: 5.29 # 10 bohr
n_features: 128
n_interactions: 6
n_rbf: 16 # exponential Bernstein polynomials
extra:
lr_cutoff: 10.0 # long-range cutoff; needed for periodic cells
electrostatics: false # without dipole or charge labels
d4_dispersion: false
The net charge and spin multiplicity come from the structure
(total_charge, spin_multiplicity). Also charge_embedding /
spin_embedding, nonlocal_interactions, attention (favor /
exact), zbl_repulsion, ewald, exp_weighting, n_residual.
ReaxFF / ReaxFF-nn#
ReaxFF: the bond-order reactive force
field (van Duin et al. 2001) and its neural variant. Bond orders come from
distances, so bonds break and form smoothly, and charges are equilibrated
at every geometry. Any parameter group can be refit by gradient descent.
Returns charges and the per-term energies.
from xnn.ffnn.models import ReaxFF
from xnn.ffnn.models.ffield import to_forcefield
model = ReaxFF("CHO_cho_2008", trainable=("Desi", "ang_val1"))
to_forcefield(model.export_library()).write("refit.frc")
The parameters are a shipped .frc field by name, a .frc path or a
ReaxFF-nn JSON library (Working with Force-field Files).
conventions picks how the terms are evaluated. "lammps", the default
for classical fields, reproduces LAMMPS pair_style reaxff with
fix qeq/reaxff, so a field refit here runs unchanged in LAMMPS.
"reaxff-nn", the default for ReaxFF-nn libraries, follows the behavior
those libraries were trained with.
OPLS / L-OPLS#
OPLS: the fixed-topology force field of
Jorgensen et al. (1996), matching OpenMM. from_atoms perceives the
bonds with RDKit and assigns the atom types from the SMARTS templates of the
parameter file, so no type names are needed. Any parameter group is
trainable, and several molecules can share one force field.
from xnn.ffnn.models import OPLS
model = OPLS.from_atoms(atoms, "oplsaa", cutoff=10.0) # or "lopls", "oplsaa-1996", "CL&P"
model = OPLS.from_atoms(atoms, "oplsaa", trainable=("dihedral_v",))
Also switch_width, fudge_lj / fudge_qq.
DREIDING / DREIDING-X6#
Dreiding: the rule-generated generic
force field of Mayo et al. (1990), matching the LAMMPS DREIDING styles.
Bond, angle and torsion parameters are generated from per-atom generators,
so it covers element combinations nobody tabulated; the generators are what
is trainable. from_atoms perceives bonds and bond orders.
from xnn.ffnn.models import Dreiding
model = Dreiding.from_atoms(atoms, "dreiding", cutoff=10.0, charges="gasteiger")
model = Dreiding.from_atoms(atoms, "dreiding/X6") # exponential-6 van der Waals
Also bond_style (harmonic / morse), angle_style, hbond,
switch_width. Set resonance types such as an amide C_R-N_R
explicitly; automatic perception does not.
Semiempirical methods: PM3, PM6, PM6-ORG, PM7#
PM6 and its siblings (sqmnn family) are the
NDDO semiempirical Hartree-Fock methods of Stewart: a minimal spd
valence basis, multipole two-center integrals, a self-consistent field per
molecule and the method’s core-core repulsion and corrections (PM7 dispersion
and hydrogen bonds, PM6-ORG D3H4). energy is the heat of formation in eV;
the outputs also hold heat_of_formation (kcal/mol), charges and
dipole. With the published parameters (the default) the methods
reproduce MOPAC; trainable makes parameter groups refittable, with
exact gradients through the SCF.
from xnn.sqmnn.models import PM6, PM7
model = PM7() # published parameters
model = PM6(trainable=["BETAS", "BETAP", "ALPB"]) # refit by gradient descent
model.parameter_set() # write_parameters() -> MOPAC EXTERNAL file
Also parameters (a set name or a MOPAC EXTERNAL file), corrections,
unrestricted, tol_density. Molecules only (no periodic cells); open
shells are spin-unrestricted. The published values are not shipped with xnn:
the first use of a method downloads them from the MOPAC 23.2.5 sources into
the model cache.
Add-ons for any model#
Wrappers that add physics to any model above, enabled from the model
section or by wrapping a model object. They combine freely and every deploy
channel carries them.
Long-range electrostatics (LES)#
LatentEwald (Cheng 2025): a small MLP maps
each atom’s features to latent charges and an Ewald sum over them adds the
long-range energy.
model:
name: mace # any model
long_range: {n_channels: 4, sigma: 1.0, dl: 2.0}
# long_range: {n_channels: 4, constrain_charge: true} # charges sum to total_charge
# long_range: {n_channels: 4, charge_solve: true} # global charge equilibration
# long_range: {n_channels: 4, method: pme} # particle-mesh sum for large cells
# long_range: {n_channels: 1, constrain_charge: true, dipole_origin: com} # fit to dipoles
# long_range: {n_channels: 1, charge_solve: true, dielectric: 78.4} # screened by a solvent
Molecules use the exact real-space sum (dl then has no effect).
Outputs gain energy_sr, energy_lr, latent_charges, plus
les_charges (the charge channel in e) and les_dipole (its dipole in
e Angstrom about dipole_origin: com, centroid or origin; zero
for cells). les_charge_weight and les_dipole_weight in optim fit
them to charges and dipole labels, which sets the scale of the latent
charges where energies and forces alone leave it loose; the keys stay apart
from the charges / dipole of models with charges of their own. For
periodic cells method: pme evaluates the same sum on a particle mesh
(smooth PME), whose cost grows as N log N instead of with the atoms times
the wave vectors; it is accurate to the spline aliasing error, about 1e-6
relative in the forces at the defaults pme_order 8 and pme_oversample
2. LES is the long-range
electrostatics for models without charges of their own; the charge-predicting
models (AIMNet2, PhysNet, SpookyNet, BAMBOO, HDNNP 3G/4G) keep their own
coulomb or electrostatics options, and LES is refused on them.
Two options tie the charges to the structure’s total_charge (its
charge label; missing means neutral). constrain_charge shifts each
structure’s charges so one channel sums to the net charge and the others to
zero (charge_weights: learned lets the model choose where the shift
goes). charge_solve goes further: that channel’s head output becomes an
electronegativity, a learned hardness per element (hardness: features
for a per-atom one) is added, and the charges minimise
chi.q + 1/2 J q^2 + E_lr under the same constraint, so every charge
responds to every other one through the Ewald kernel; the energy gains
energy_charge. Charged structures must be molecules (a cell’s k = 0
term is dropped). With fragments: true each molecule or ion is
constrained on its own (plain charge equilibration lets charge flow between
distant fragments): fragments are the covalently bonded groups, ions in
ion_charges (alkali, alkaline-earth and halide ions by default) always
stand alone, and a fragment’s charge comes from the per-atom
fragment_charges label of the structure file when present, else from
that table.
dielectric divides the charge channel’s long-range energy (and its
Coulomb coupling in the charge solve) by a constant, 1 by default; set the
solvent’s value (78.4 for water) when the labels come from a continuum
solvent model, so charges fitted to physical ones carry the screened
interaction the labels contain. learn_dielectric: true fits it, through
its logarithm, from dielectric as the start. The reported charges and
dipole are unscreened.
Dispersion (DFT-D4, DFT-D3)#
D4Dispersion and
D3Dispersion reproduce dftd4 and
simple-dftd3 to floating-point precision; they also stand alone as the
models d4 and d3.
model:
name: mace
dispersion: true # PBE0-D4 defaults
# dispersion: {s6: 1.0, s8: 1.2, a1: 0.4, a2: 5.0} # D4, explicit damping
# dispersion: {name: d3, damping: bj, s9: 1.0} # D3(BJ) with the three-body term
For condensed-phase work shorten the cutoffs (the defaults reproduce the reference codes) and add switching windows so MD stays smooth:
dispersion: {cutoff_pair: 12.0, switch_width_pair: 2.0,
cutoff_triple: 10.0, switch_width_triple: 1.0,
tail_correction: true} # D4: add back the cut pair tail
The net charge comes from total_charge. D4 picks its charge solver by
size (regime: dense | large | auto); in MD, eeq_reuse carries the
solve between steps (Deployment). Outputs gain energy_disp and,
for D4, eeq_charges. A model trained on labels with the dispersion
removed records it in subtracted_dispersion (Deployment).