Config Schema¶
Complete reference for every field in a TrainCraft TOML config file.
All fields are validated by pydantic v2. Unknown fields raise a ValidationError.
Paths¶
Every filesystem path in the config (outdir, dataset.path, source/builder
path/file, model_path, pt_train_file, slurm.sif_dir, …) supports:
~expansion — a leading~resolves to your home directory, sooutdir = "~/tests/runs"lands in$HOME/tests/runs, not a literal~folder under the working directory.- Environment variables —
$VAR/${VAR}are expanded, e.g.path = "$SCRATCH/in.xyz".
Relative input paths (e.g. [geometry.source] path) are resolved against the
config file's directory; relative output paths (outdir, dataset.path) are
left relative to the working directory. Expansion happens before this, so an
expanded ~/$VAR path is absolute and is used as-is. Expansion uses the home
and environment of wherever the config is parsed — on a different machine,
prefer absolute paths or environment variables that exist there.
[run]¶
Global settings for the pipeline run.
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
"traincraft_run" |
Name of the run; becomes the workspace subdirectory |
outdir |
str |
"runs" |
Root output directory (relative to working dir; ~/$VAR expanded — see Paths) |
seed |
int \| null |
42 |
RNG seed for reproducibility. null → random |
[geometry]¶
Specifies the initial structure(s). Exactly one of source or builder must be
set. Transforms are optional and applied in order.
[geometry] # section header (optional if using sub-tables)
replicate = 1 # build N independent variants (see below)
[geometry.source] # OR [geometry.builder]
type = "..."
[[geometry.transforms]]
type = "..."
| Field | Type | Default | Description |
|---|---|---|---|
replicate |
int |
1 |
Build the geometry N times. A source/builder with a seed field gets a distinct derived seed per replica (base seed + replica index), so stochastic builders yield N independent seed structures — e.g. 20 different liquid packings or alloy arrangements. |
Multi-structure geometries. A geometry may resolve to several seed
structures — via replicate, a smiles source with n_conformers > 1, or a
file source with index = ":". All of them land in
structures/initial.extxyz, and every seed is sampled by the sampling
stage (candidates are pooled before selection, and each candidate records its
parent seed in its provenance).
Sources¶
type = "file"¶
| Field | Type | Default | Description |
|---|---|---|---|
path |
str |
required | Path to any ASE-readable file (extxyz, POSCAR, CIF, xyz, …) |
index |
int \| str |
-1 |
ASE image index into a multi-frame file: an int picks one frame (-1 = last), a slice string like ":" makes every frame a seed structure — how a trajectory or an externally generated set enters the pipeline |
type = "scratch"¶
| Field | Type | Default | Description |
|---|---|---|---|
molecule |
str \| null |
null |
ASE g2 molecule name (e.g. "H2O", "CO2") |
bulk |
str \| null |
null |
ASE bulk symbol (e.g. "Cu", "Si") |
Exactly one of molecule or bulk must be set.
type = "smiles"¶
Requires RDKit (pixi install -e science).
| Field | Type | Default | Description |
|---|---|---|---|
smiles |
str |
required | SMILES string (canonicalised automatically) |
n_conformers |
int |
1 |
Number of ETKDG conformers to generate; > 1 yields one seed structure per conformer |
optimize |
bool |
true |
Run MMFF geometry optimisation |
seed |
int \| null |
null |
RNG seed for conformer embedding |
vacuum |
float |
6.0 |
Vacuum padding in Å |
type = "url"¶
| Field | Type | Default | Description |
|---|---|---|---|
url |
str |
required | URL to download (http/https/file://) |
format |
str \| null |
null |
ASE format override (inferred from suffix if null) |
timeout |
float |
30.0 |
Download timeout in seconds |
Builders¶
Shared blocks: Species and AlloyComponent¶
Two small blocks are reused across builders.
Species — one component of a mixture. Used by liquid,
surface_packing and filled_nanotube (as a [[geometry.builder.species]]
list). Provide exactly one identity and at most one amount:
| Field | Type | Default | Description |
|---|---|---|---|
molecule_name |
str \| null |
null |
ASE g2 name (identity) |
smiles |
str \| null |
null |
SMILES, built with RDKit (identity) |
file |
str \| null |
null |
Path to a structure file (identity) |
count |
int \| null |
null |
Absolute number of copies |
ratio |
float \| null |
null |
Relative share (apportioned from n_molecules) |
Within one mixture use a single amount style — all count, or all ratio
(then the builder's n_molecules is the total). A bare species is one copy
(count mode) or an equal share (ratio mode).
AlloyComponent — one substituent of a random solid solution. Used by
crystal, slab, surface_adsorbate and surface_packing (as a
[[geometry.builder.composition]] list):
| Field | Type | Default | Description |
|---|---|---|---|
element |
str |
required | Element to substitute in |
ratio |
float |
required | Fraction of host sites to replace, in (0, 1] |
Component ratios must sum to ≤ 1 (the remainder stays the host element);
substituted sites are picked with the builder's seed.
type = "nanotube"¶
| Field | Type | Default | Description |
|---|---|---|---|
n |
int |
8 |
Chiral index n |
m |
int |
0 |
Chiral index m (m=0 → zigzag; n=m → armchair) |
length |
int |
1 |
Number of unit cells along the tube axis |
bond |
float |
1.42 |
C–C bond length in Å |
vacuum |
float |
6.0 |
Radial vacuum in Å |
type = "filled_nanotube"¶
A carbon nanotube randomly filled with molecules (the original "fillMyTubes"
use case). Packmol packs n_molecules guests into a cylinder coaxial with the
tube and just narrower than its wall. The tube carbons are tagged framework
(tc_fragment == -1); each guest molecule is its own fragment. Requires the
Packmol binary (pixi install -e science).
| Field | Type | Default | Description |
|---|---|---|---|
n |
int |
10 |
Chiral index n (default is roomy armchair) |
m |
int |
10 |
Chiral index m |
length |
int |
6 |
Unit cells along the tube axis |
bond |
float |
1.42 |
C–C bond length in Å |
vacuum |
float |
8.0 |
Radial vacuum in Å |
molecule_name |
str \| null |
null |
Single guest: ASE g2 name (e.g. "H2O") |
smiles |
str \| null |
null |
Single guest: SMILES (needs RDKit) |
file |
str \| null |
null |
Single guest: path to a structure file |
species |
list[Species] |
[] |
A mixture of guests (alternative to the single-guest fields) |
n_molecules |
int |
4 |
Single mode: copies; ratio mixture: total guests |
radial_margin |
float |
0.0 |
Extra room taken off the packing cylinder (Å) |
axial_margin |
float |
1.5 |
Inset at each tube end (Å; avoids PBC clashes) |
tolerance |
float |
2.0 |
Packmol minimum separation (Å) |
pbc |
bool |
true |
Periodic along the tube axis |
seed |
int \| null |
null |
Packmol seed |
Use either a single guest (molecule_name/smiles/file) or a
species mixture — not both.
No wall overlap. The tube is handed to Packmol as a fixed obstacle, so
every guest atom is kept at least tolerance from the wall in true 3D — that is
what prevents overlap. The packing cylinder is only sized to keep guest atom
centres inside the carbon van der Waals shell, leaving the full cavity usable
(raise tolerance for more breathing room, or set radial_margin for extra).
If the tube is too narrow/short the builder raises a clear error — widen
(n/m), lengthen (length), or reduce the margins.
type = "molecule"¶
| Field | Type | Default | Description |
|---|---|---|---|
name |
str \| null |
null |
ASE g2 molecule name |
smiles |
str \| null |
null |
SMILES string (Phase 2) |
vacuum |
float |
6.0 |
Vacuum in Å |
type = "crystal"¶
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
required | Element symbol or compound (e.g. "Cu", "NaCl") |
crystalstructure |
str \| null |
null |
fcc, bcc, hcp, diamond, rocksalt, wurtzite, … |
a |
float \| null |
null |
Primary lattice constant in Å |
b |
float \| null |
null |
b lattice constant (orthorhombic) |
c |
float \| null |
null |
c lattice constant (HCP, tetragonal) |
cubic |
bool |
false |
Use the cubic conventional cell |
orthorhombic |
bool |
false |
Use the orthorhombic cell |
supercell |
[int, int, int] |
[1, 1, 1] |
Supercell repetitions |
defects |
list[DefectSpec] |
[] |
Point defects to introduce |
composition |
list[AlloyComponent] |
[] |
Random solid solution (mixed solid) |
seed |
int \| null |
null |
Seeds the random site substitution |
DefectSpec:
| Field | Type | Default | Description |
|---|---|---|---|
kind |
"vacancy" \| "substitution" \| "interstitial" |
required | Defect type |
index |
int \| null |
null (→ 0) |
Atom index in the pre-defect supercell |
element |
str \| null |
null |
New element (substitution/interstitial) |
position |
[float, float, float] \| null |
null |
Insertion position (interstitial) |
cartesian |
bool |
false |
If true, position is Cartesian (Å); else fractional |
type = "slab"¶
| Field | Type | Default | Description |
|---|---|---|---|
element |
str |
required | Substrate element |
facet |
see below | "fcc111" |
Named facet (ignored if miller is set) |
miller |
[int, int, int] \| null |
null |
Miller indices (overrides facet) |
crystalstructure |
str \| null |
null |
Needed for Miller mode |
a |
float \| null |
null |
Lattice constant for Miller mode |
c |
float \| null |
null |
c lattice constant for HCP |
cubic |
bool |
false |
Cubic cell for Miller mode |
layers |
int |
4 |
Slab thickness in atomic layers (Miller mode) |
size |
[int, int, int] |
[3, 3, 4] |
[nx, ny, nz]; nz=layers in facet mode |
vacuum |
float |
12.0 |
Total vacuum in Å |
orthogonal |
bool |
false |
Request an orthogonal cell (facet mode) |
periodic |
bool |
false |
Keep periodic along the surface normal (Miller mode) |
composition |
list[AlloyComponent] |
[] |
Random solid solution (mixed-solid slab) |
seed |
int \| null |
null |
Seeds the random site substitution |
facet values: fcc111, fcc100, fcc110, bcc110, bcc100, bcc111, hcp0001
type = "layered"¶
| Field | Type | Default | Description |
|---|---|---|---|
material |
"graphene" \| "hbn" \| "mx2" |
"graphene" |
2D crystal type |
formula |
str \| null |
null |
MX₂ formula (e.g. "MoS2", "WSe2") |
a |
float \| null |
null |
In-plane lattice constant override |
size |
[int, int] |
[1, 1] |
In-plane repetitions of the primitive cell |
n_layers |
int |
2 |
Number of layers to stack |
interlayer_spacing |
float |
3.35 |
Vertical layer spacing in Å |
stacking |
"AA" \| "AB" |
"AB" |
Stacking order |
twist |
float |
0.0 |
Twist angle in degrees (non-zero → non-periodic flake) |
vacuum |
float |
15.0 |
Total vacuum in Å |
type = "surface_adsorbate"¶
| Field | Type | Default | Description |
|---|---|---|---|
element |
str |
required | Substrate element |
facet |
str |
"fcc111" |
Named facet |
size |
[int, int, int] |
[3, 3, 4] |
Slab size |
vacuum |
float |
12.0 |
Total vacuum in Å |
composition |
list[AlloyComponent] |
[] |
Random solid solution (mixed-solid slab) |
seed |
int \| null |
null |
Seeds the random site substitution |
molecule_name |
str \| null |
null |
ASE g2 adsorbate name |
smiles |
str \| null |
null |
SMILES adsorbate |
file |
str \| null |
null |
Path to adsorbate file |
site |
str |
"ontop" |
Initial site: ontop, bridge, hollow, fcc, hcp |
height |
float |
2.0 |
Initial height above slab top (Å) |
offset |
[float, float] \| null |
null |
In-plane offset from the site |
Exactly one of molecule_name, smiles, or file must be set.
type = "surface_packing"¶
All surface_adsorbate substrate parameters (including composition for a
mixed-solid slab), plus:
| Field | Type | Default | Description |
|---|---|---|---|
species |
list[Species] |
[] |
A mixture of adsorbates (alternative to a single molecule) |
n_molecules |
int |
4 |
Single mode: copies; ratio mixture: total adsorbates |
tolerance |
float |
2.0 |
Packmol intermolecular distance (Å) |
region_height |
float |
8.0 |
Height of the packing box above the slab (Å) |
gap |
float |
2.0 |
Clearance between slab top and packing box (Å) |
seed |
int \| null |
null |
Packmol seed / substitution seed |
Use either a single adsorbate (molecule_name/smiles/file) or a
species mixture — not both.
type = "liquid"¶
Packs a species mixture into a periodic box (a liquid, a solvent blend, or
confined bulk). Requires the Packmol binary (pixi install -e science).
| Field | Type | Default | Description |
|---|---|---|---|
species |
list[Species] |
required | The mixture to pack (≥ 1 species) |
n_molecules |
int \| null |
null |
Total for ratio-based mixtures |
box |
[float, float, float] \| null |
null |
Explicit cell edges in Å |
density |
float \| null |
null |
g/cm³; a cubic box is sized when box is unset |
tolerance |
float |
2.0 |
Packmol min separation / wall inset (Å) |
pbc |
bool |
true |
Periodic cell |
seed |
int \| null |
null |
Packmol seed |
Set exactly one of box or density. Ratio-based mixtures need n_molecules.
Transforms¶
type = "supercell"¶
| Field | Type | Default | Description |
|---|---|---|---|
repeat |
[int, int, int] |
[1, 1, 1] |
Repetitions along each cell vector |
type = "vacuum"¶
| Field | Type | Default | Description |
|---|---|---|---|
amount |
float |
6.0 |
Total vacuum to add (Å); split equally on both sides |
type = "perturb"¶
| Field | Type | Default | Description |
|---|---|---|---|
stddev |
float |
0.05 |
Standard deviation of Gaussian displacements (Å) |
type = "strain"¶
| Field | Type | Default | Description |
|---|---|---|---|
hydrostatic |
float \| null |
null |
Isotropic engineering strain (e.g. 0.02 = +2%) |
voigt |
[float×6] \| null |
null |
(e_xx, e_yy, e_zz, e_yz, e_xz, e_xy) |
Exactly one of hydrostatic or voigt must be set.
type = "rotate"¶
| Field | Type | Default | Description |
|---|---|---|---|
angle |
float |
required | Rotation angle in degrees |
axis |
"x" \| "y" \| "z" \| [float×3] |
"z" |
Rotation axis |
rotate_cell |
bool |
false |
Also rotate the periodic cell vectors |
type = "set_pbc"¶
| Field | Type | Default | Description |
|---|---|---|---|
pbc |
bool \| [bool×3] |
true |
New PBC flags |
[calculator]¶
type = "emt"¶
No parameters. ASE's built-in force field — works for metals, zero deps.
type = "tblite"¶
| Field | Type | Default | Description |
|---|---|---|---|
method |
str |
"GFN2-xTB" |
"GFN2-xTB" or "GFN1-xTB" |
type = "xtb"¶
Same as tblite. Uses the xtb-python binding instead.
type = "mace"¶
| Field | Type | Default | Description |
|---|---|---|---|
model |
str |
"mace-mp0" |
Foundation model: "mace-mp0" or "mace-off23" |
model_path |
str \| null |
null |
Path to a local .model checkpoint (overrides model) |
device |
str |
"cpu" |
"cpu" or "cuda" |
default_dtype |
str |
"float32" |
"float32" or "float64" |
[sampling]¶
type = "md"¶
Langevin NVT molecular dynamics.
| Field | Type | Default | Description |
|---|---|---|---|
temperature |
float |
500.0 |
Temperature in K |
steps |
int |
1000 |
Total MD steps |
interval |
int |
20 |
Save a frame every N steps |
timestep |
float |
1.0 |
Timestep in femtoseconds |
friction |
float |
0.02 |
Langevin friction coefficient (1/fs) |
type = "rattle"¶
HiPhive random displacement sampling. Requires pixi install -e science.
| Field | Type | Default | Description |
|---|---|---|---|
method |
"mc" \| "standard" |
"mc" |
Rattle method |
n_structures |
int |
10 |
Number of rattled structures to generate |
std |
float |
0.12 |
Displacement standard deviation (Å) |
min_distance |
float |
1.3 |
Minimum interatomic distance (Å) |
type = "monte_carlo"¶
Metropolis MC with rigid-body translate/rotate and optional conformer-swap moves.
| Field | Type | Default | Description |
|---|---|---|---|
steps |
int |
1000 |
Total MC moves |
temperature |
float |
500.0 |
Metropolis temperature (K) |
interval |
int |
20 |
Save a snapshot every N accepted moves |
p_translate |
float |
0.5 |
Probability of a translate move |
p_rotate |
float |
0.4 |
Probability of a rotate move |
p_conformer |
float |
0.1 |
Probability of a conformer-swap move |
max_translate |
float |
0.5 |
Maximum displacement per translate move (Å) |
max_rotate |
float |
30.0 |
Maximum rotation per rotate move (degrees) |
refresh_fragments |
bool |
false |
Re-infer fragments after each accepted move |
refresh_scale |
float |
1.2 |
Bond-radius scale for infer_fragments |
seed |
int \| null |
null |
RNG seed |
record |
"accepted" \| "trials" |
"accepted" |
What to snapshot each interval (see below) |
record = "trials" for unbiased MLIP training data
Metropolis MC samples the Boltzmann ensemble: it accepts downhill moves and
only occasionally accepts uphill ones, so the default record = "accepted"
snapshots a chain concentrated near equilibrium. For training an interatomic
potential you usually want the opposite — a broad distribution including
high-energy, distorted geometries, so the model isn't biased toward the minimum.
record = "trials" snapshots every proposed move, accepted or rejected.
Those rejected proposals are exactly the high-energy, off-equilibrium points a
Metropolis filter would throw away — and their energy is already computed during
the run, so keeping them is nearly free. Each frame records
tc_provenance.extra.mc_accepted so you can tell proposals from the chain.
(For even broader force coverage, combine with rattle or high-temperature MD.)
type = "scan"¶
Deterministic grid scan of one fragment's position/orientation — the exact,
combinatorial counterpart to the stochastic samplers above. It enumerates the
Cartesian product of a translate and/or rotate grid applied to the chosen
fragment, so it's the tool for potential-energy-surface scans, binding/
dissociation curves and orientation grids. Energies are attached by the
[labeling] stage, like every sampler.
| Field | Type | Default | Description |
|---|---|---|---|
fragment |
int \| "all" |
0 |
tc_fragment id to move (adsorbate/guest = 0); "all" moves the whole system |
translate |
ScanAxis \| null |
null |
Grid of displacements (Å) along an axis, added to the fragment's position |
rotate |
ScanAxis \| null |
null |
Grid of rotations (degrees) about an axis through the fragment centroid |
At least one of translate / rotate is required. A ScanAxis is a small
table — steps values evenly spaced over [start, stop] (both ends inclusive):
| Field | Type | Default | Description |
|---|---|---|---|
axis |
"x" \| "y" \| "z" or [x, y, z] |
"z" |
Axis to translate along / rotate about |
start |
float |
— | First grid value (Å for translate, degrees for rotate) |
stop |
float |
— | Last grid value |
steps |
int |
5 |
Number of grid points (so translate.steps × rotate.steps candidates) |
[sampling]
type = "scan"
fragment = 0
translate = { axis = "z", start = 0.0, stop = 3.0, steps = 13 } # 13-point binding curve
rotate = { axis = "x", start = 0.0, stop = 180.0, steps = 7 } # × 7 orientations = 91 frames
Keep every grid point
A scan is meant to be exhaustive, so pair it with a generous
[selection] budget (or drop the diversity step), or the
funnel will thin your grid.
[selection]¶
| Field | Type | Default | Description |
|---|---|---|---|
steps |
list[str] |
["physicality", "dedup", "diversity"] |
Ordered filter stages |
budget |
int \| null |
50 |
Maximum frames to keep. null = no cap |
min_distance |
float |
0.7 |
Physicality threshold (Å) |
[dataset]¶
| Field | Type | Default | Description |
|---|---|---|---|
path |
str |
"dataset" |
Path (without extension) for the output extxyz dataset |
format |
"extxyz" |
"extxyz" |
Output format (only extxyz is currently supported) |
[labeling]¶
Labels the selected frames with an (expensive) calculator — distinct from the
cheap [calculator] that drives sampling. Presence of this section enables the
label stage.
| Field | Type | Default | Description |
|---|---|---|---|
calculator |
calculator config | required | A [labeling.calculator] sub-table; usually fhi_aims or qe (any calculator works — emt stands in for DFT in demos) |
on_error |
"skip" \| "raise" |
"skip" |
What one failed frame does to the batch. "skip" quarantines the frame's input in failed.extxyz, records the error in receipts.jsonl + manifest.json, and continues; "raise" aborts on the first failure. If every frame fails the stage raises regardless. |
[labeling]
on_error = "skip" # partial success is the normal outcome at scale
[labeling.calculator]
type = "fhi_aims"
xc = "pbe"
species_defaults = "tight"
properties = ["polarizability"] # E/F/stress always; + dipole/polarizability on request
Bookkeeping is a fixed set of append-only files (Lustre-safe at 10⁵–10⁶
frames — no directory per frame): receipts.jsonl gets one line per attempt
(fingerprint of geometry + level of theory, status, wall time, error) and
done.extxyz journals each labeled frame as it completes. Reruns resume:
journaled frames whose fingerprint still matches are reused without
recomputation, failed frames are retried, and manifest.json reports
n_attempted / n_frames / n_failed / n_reused with per-frame error
messages. File-IO DFT codes run in one reusable work/ scratch dir, snapshotted
to failed/frame_N/ when a frame fails.
See Calculators & DFT Labeling for the DFT calculator fields and the environment-injected run command.
[training]¶
Trains a MACE model on the dataset (or the labelled frames). Presence of this
section enables the train stage, which runs after dataset. Defaults follow
Tompa et al. (arXiv:2606.12704); see the
Training concept page.
type = "mace"¶
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
"mace_model" |
Output model name → model/<name>.model |
foundation_model |
str \| null |
"medium" |
Model to fine-tune from: small/medium/large, mace-mp0, mace-off23, or a path. null with strategy="scratch" trains from scratch |
strategy |
"multihead" \| "naive" \| "scratch" |
"multihead" |
Fine-tuning recipe. multihead = replay against forgetting |
heads |
list[str] |
["energy", "forces"] |
Properties to learn: any of energy, forces, stress, dipole, polarizability. Selects the MACE model type + loss |
e0s |
str |
"foundation" |
Isolated-atom energies: "foundation" (reuse), "average" (avoid), or a JSON Z→energy dict |
valid_fraction |
float |
0.1 |
Held-out validation fraction (deterministic given [run].seed) |
pt_train_file |
str \| null |
null |
Replay data for multihead fine-tuning: "mp" or a path |
num_samples_pt |
int |
30000 |
Number of replay samples |
weight_pt, weight_ft |
float |
1.0, 1.0 |
Replay-head / fine-tune-head weights |
energy_weight, forces_weight |
float |
10.0, 10.0 |
Loss weights (paper: energy-prioritised, constant) |
stress_weight, dipole_weight, polarizability_weight |
float |
1.0 |
Per-head loss weights (used when that head is present) |
lr |
float |
1e-3 |
Learning rate |
weight_decay |
float |
0.0 |
Weight decay (paper: keep 0 for fine-tuning) |
ema |
bool |
true |
Use exponential moving average of weights |
ema_decay |
float |
0.995 |
EMA decay (paper: > 0.99 for fine-tuning) |
max_num_epochs |
int |
200 |
Training epochs |
batch_size |
int |
10 |
Batch size |
swa |
bool |
false |
Stochastic weight averaging (final-stage) |
hidden_irreps |
str \| null |
null |
Model size for scratch (e.g. "128x0e + 128x1o") |
r_max |
float |
5.0 |
Cutoff radius (Å) for scratch |
device |
str |
"cpu" |
"cpu" or "cuda" |
default_dtype |
"float32" \| "float64" |
"float64" |
Training precision |
seed |
int \| null |
null |
Trainer seed (also used for the split) |
extra |
dict |
{} |
Arbitrary mace_run_train flags (without --); wins over the defaults above |
[training]
type = "mace"
name = "cu_finetune"
foundation_model = "medium"
strategy = "multihead"
heads = ["energy", "forces", "stress"]
e0s = "foundation"
pt_train_file = "mp"
device = "cuda"
# escape hatch: pass any raw mace_run_train flag (overrides a default)
[training.extra]
scheduler = "ReduceLROnPlateau"
Multi-head model selection
heads chooses the MACE architecture automatically: dipole →
EnergyDipolesMACE/AtomicDipolesMACE, polarizability →
AtomicDielectricMACE. Override --model/--loss/keys via [training.extra]
if your MACE version differs.
[validation]¶
Measures a model against reference-labeled frames and writes
validation/quality_report.json — per-property parity metrics (RMSE/MAE,
per-element force errors), optional pass/fail thresholds, and per-frame energy
parity data. Presence of this section enables the validate stage (after
train). The same report is what a human reads, what a dashboard plots, and
what the future active-learning loop will use as its convergence criterion.
| Field | Type | Default | Description |
|---|---|---|---|
calculator |
calculator config | null | null |
The model under test. Default: the model the train stage produced (from model/manifest.json), loaded as a mace calculator |
test_file |
path | null | null |
Explicit reference set (tc_* or training REF_* extxyz). Default: the training holdout model/valid.xyz (never seen in training), else the latest labeled/dataset artifact — beware that last fallback overlaps the training set |
properties |
list | null | null |
Which properties to check (energy/forces/stress/dipole/polarizability). Default: everything the reference frames carry |
max_frames |
int \| null |
null |
Cap the number of frames compared (quick checks) |
thresholds |
dict |
{} |
Metric name → maximum allowed value; the report records each check and an overall passed. Unknown metric names fail fast |
Metric names for thresholds: energy_rmse, energy_mae,
energy_rmse_per_atom, energy_mae_per_atom (eV, eV/atom), forces_rmse,
forces_mae (eV/Å), and <property>_rmse/<property>_mae for
stress/dipole/polarizability when present.
[validation]
# calculator omitted → validates the model just trained by [training]
thresholds = { forces_rmse = 0.05, energy_mae_per_atom = 0.005 }
A frame whose energy/forces prediction fails is skipped and recorded (same
philosophy as [labeling]'s on_error="skip"); optional properties the model
cannot produce are dropped per property without voiding the frame.