Training a Neural Network Potential#
To train a neural network potential (NNP) with dataset.pkl, write a configuration file train.yaml that specifies data preprocessing, model architecture, training protocol, and metrics. This page walks through a minimal SchNet setup. The full multi-architecture reference is enerzyme/config/train.yaml.
Datahub#
Datahub:
The Datahub section connects your dataset to the model. At minimum specify:
data_path— path to the dataset (relative paths are allowed)data_format—pickle,npz, orhdf5
Then define features (model inputs) and targets (quantities to fit). Each key is a standard Enerzyme field name; the value is the attribute name in your dataset (leave empty if they match).
Standard Data Field Conventions#
Physical quantities use capitalized standard names. Atomic quantities end with a:
N— number of atomsRa— coordinatesZa— atomic numbersE— total energyFa— forcesQa— atomic chargesQ— total chargeM2— dipole moment
You may rename dataset attributes freely as long as the mapping is consistent, e.g. Ra: xyz instead of Ra: coordinates.
Preprocessing the Dataset#
NNPs in Enerzyme are graph neural networks. Optional preprocessing improves efficiency and accuracy:
neighbor_list: full— precompute all-pairs neighbor lists before trainingcompressed: true— shareZa,N,Q, and neighbor lists across frames of the same stoichiometry (same atom order)preload: true— reuse cached HDF5 underprocessed_dataset_<hash>/when config hash matchestransforms.negative_gradient: true— flip gradient sign whenFastores QC gradients, not forcestransforms.atomic_energy— subtract per-atom reference energies from a CSV (atom_type,atomic_energy)
Note
The canonical config uses compressed, not compression. Older single-dataset YAML without datasets: still accepts the legacy layout; new projects should follow train.yaml.
Final Datahub Configuration#
Datahub:
data_path: "dataset.pkl"
data_format: "pickle"
features:
N: "number_of_atoms"
Ra: "coordinates"
Za: "atomic_numbers"
Q: "total_charge"
targets:
E: "energy"
Fa: "forces"
Qa: "atomic_charges"
M2: "dipole"
neighbor_list: full
compressed: true
preload: true
transforms:
atomic_energy: "atomic_energy.csv"
negative_gradient: true
Modelhub#
Models live under internal_FFs (built into Enerzyme) or external_FFs (NequIP, XPaiNN, etc.). Each model has a unique ID (e.g. FF01).
Choosing an architecture#
Architecture |
Good for |
Notes |
|---|---|---|
SchNet |
First tutorial / baseline |
Charge + dipole capable |
PhysNet |
Production charge-aware PES |
Electrostatics, D3 layers |
SpookyNet |
Large organic / mixed systems |
Similar feature set |
MACE |
Equivariant accuracy |
Higher compute cost |
NequIP |
External equivariant model |
Requires |
XPaiNN |
External XPaiNN via XequiNet |
Extra pip packages |
Enable exactly one model (active: true) when starting out.
SchNet configuration#
SchNet is fully internal—no extra pip packages. Key entries:
architecture: SchNetbuild_params—dim_embedding,num_rbf,max_Za,cutoff_sr,Hartree_in_E,Bohr_in_Rlayers— modular stack ending withForcefor analytic forcesloss— weighted sum over targets (convert force weights to your energy/length units)Metric— per-model validation metric for early stopping (same weights as loss is common)
Note
Hartree_in_E and Bohr_in_R convert internal atomic units to your dataset units. For Ha and Å, use 1 and 0.5291772108. For eV and Å, use ~27.2 and ~0.529.
Final Modelhub configuration#
Modelhub:
internal_FFs:
FF01:
suffix:
architecture: SchNet
active: true
build_params:
dim_embedding: 128
num_rbf: 128
max_Za: 94
cutoff_sr: 5.0
Hartree_in_E: 1
Bohr_in_R: 0.5291772108
layers:
- name: RangeSeparation
- name: GaussianSmearing
- name: RandomAtomEmbedding
- name: Core
params:
num_interactions: 4
hidden_channels: 128
- name: AtomicAffine
- name: ChargeConservation
- name: AtomicCharge2Dipole
- name: ElectrostaticEnergy
- name: EnergyReduce
- name: Force
loss:
rmse:
Fa: 52.917721
Qa: 1
E: 1
M2: 1.8897261
Q: 1
Metric:
E:
rmse: 1
Fa:
rmse: 52.917721
Qa:
rmse: 1
M2:
rmse: 1.8897261
Q:
rmse: 1
Trainer#
Splitter#
Random splitting is configured under Trainer.Splitter:
parts— partition names (at leasttrainingandvalidation)ratios— fractions or absolute counts, in the same order aspartsseed,save,preload— reproducibility and index caching
Training loop#
Each epoch loads mini-batches of batch_size using num_workers processes. Training stops at max_epochs or when validation judge_score fails to improve for patience epochs.
Optimizer and scheduler#
Enerzyme uses Adam with a linear warmup scheduler (schedule: linear, warmup_ratio).
System settings#
Set cuda: true for GPU training and dtype: float32 (or float64). Set seed for reproducibility.
Caution
CUDA nondeterminism may still cause run-to-run differences even with a fixed seed.
Final Trainer configuration#
Trainer:
Splitter:
method: random
parts:
- training
- validation
- test
preload: true
ratios:
- 0.7
- 0.1
- 0.2
save: true
seed: 42
batch_size: 64
cuda: true
dtype: float32
schedule: linear
learning_rate: 0.001
max_epochs: 10000
num_workers: 10
patience: 50
seed: 42
warmup_ratio: 0.001
Running the training job#
enerzyme train -c train.yaml -o .
Output artifacts#
After training, the output directory typically contains:
config.yaml— resolved configuration (keep this for predict/simulate)processed_dataset_<hash>/— preprocessed HDF5 cachelogs/— training logs, metrics, early-stopping tracesFF01/(or your model ID) —best/andlast/checkpoints
Use enerzyme/config/train.yaml when you need multi-dataset Datahub, external models, EMA, Lightning multi-GPU, or pretraining paths.