Design of xnn#
xnn is organized by model family (gnn, E(3)-equivariant graph
networks; cnn, continuous-filter convolutions; dnn, descriptor +
per-element networks), with everything shared across families factored into
common. A component lives with the family that uses it, or in common
when more than one family needs it; each layer still stands alone and can be
imported on its own.
src/xnn/
common/ shared across all families
data/ AtomicGraph (the one data object), PBC neighbor list,
AtomicDataset, batching
featurizers/ Featurizer base + shared basis functions
(GaussianRBF, CosineCutoff)
config/ one dataclass schema; loaders for yaml/argparse/hydra
models/ InteratomicPotential interface + registry +
ForceStressOutput + ops (scatter_sum), the add-on
terms LES (les), DFT-D4 (d4) and DFT-D3 (d3) on shared
dispersion machinery (dispersion)
train/ Trainer (batch + device aware), weighted
energy/force/stress loss
benchmark/ score pre-trained models on a dataset (metrics,
atomization energy, report writers)
deploy/ ASE Calculator, LAMMPS/TorchScript export
cli/ the `xnn` command
gnn/ E(3)-equivariant GNNs (need e3nn)
featurizers/ SphericalHarmonicEdgeEmbedding, BesselRBF, PolynomialCutoff
models/ base (EquivariantGNN, GNNPotential), blocks,
nequip, mace, allegro, cace
cnn/ continuous-filter conv net
models/ schnet
dnn/ descriptor + per-element networks
featurizers/ symmetry functions, AEV
models/ base (DescriptorPotential), hdnnp, ani, physnet
Four ideas hold the package together.
1. One data object#
Every model consumes an AtomicGraph
and returns {"node_energy", "energy"}. Molecular vs. periodic is
invisible to models; periodicity lives only in
edge_vectors():
where \(\mathbf{s}_{ij}\) is the integer cell shift of the edge and \(\mathbf{h}\) the cell matrix. Because the edge vectors are computed from positions and cell inside the graph, forces and stress stay differentiable end to end.
2. Featurizers are first-class#
A Featurizer (a subclass of
nn.Module) turns a graph into invariant descriptors (symmetry functions,
AEV) or equivariant edge attributes (spherical harmonics). Descriptor models
(HDNNP, ANI) and GNNs (NequIP, MACE, Allegro) are thin compositions over
featurizers, so the featurization is reusable and inspectable on its own:
from xnn.dnn.featurizers import AEV
from xnn.gnn.featurizers import SphericalHarmonicEdgeEmbedding
descriptor = AEV(species=[1, 6, 8])(graph) # (N, D) invariant
edges = SphericalHarmonicEdgeEmbedding(l_max=2)(graph) # equivariant
3. Forces and stress in one place#
ForceStressOutput wraps any model and
differentiates the predicted energy with respect to positions (forces,
\(\mathbf{F}_i = -\partial E / \partial \mathbf{r}_i\)) and a symmetric
strain (stress). Models never implement forces themselves: a model is just
an energy function, and the physics of differentiation is written once.
4. Extensibility via registry + one config, three frontends#
A model becomes available everywhere with two ingredients: the
@register_model("name") decorator and a from_config classmethod. The
registry (build_model()) dispatches on
cfg.model.name, and the single Config
dataclass is filled from any of three frontends (YAML, argparse, or
Hydra), which all funnel into the same place. Upstream config spellings are
handled by a loader-level key-translation registry, never by per-model
aliases (see Reuse Upstream MACE/NequIP Configs).
Everything downstream (data, featurizers, autograd forces/stress, training, ASE/LAMMPS deployment) is identical across all models.