Feedstock reads the output of an ab initio code and writes geometry files you can train a machine-learning force field on. It also partitions those files into training sets, and generates the next round of DFT inputs from them. Three steps, each driven by one YAML file.
It does not run calculations and it does not train models. It does the bookkeeping on either side of both.
That bookkeeping is duller than it sounds and there is a lot of it. Every code prints its energies and forces in its own layout and its own units. Frames have to be counted, normalised, partitioned, and handed to a trainer in a format it recognises. Then the next batch of calculations has to be set up, which means copying a template directory a few hundred times with the right geometry in each one.
The usual approach is a folder of one-off scripts with absolute paths near the top. They break the moment you change machine. You cannot re-run one without opening it in an editor first, and by the time you want to know how a dataset was made, the script that made it has been edited three times.
A YAML config fixes that mainly because you can commit it next to the data.
validate will also tell you the config is wrong before a job that runs for six
hours starts.
Steps are not chained, deliberately. Convert can take hours on a large trajectory and a failed Split should not cost you that, so each step reads and writes files on disk and you re-run only the one that broke.
ab initio output --Convert--> geometry file --Split--> train / val / test
(aims, cp2k, orca) | |
^ | v
| | MLFF training
+----- your cluster <--Calc-make-- DFT input folders
A geometry file holds atomic configurations: positions, cell, energies, forces.
It is written as extxyz or npz, and it is what every workflow reads and
writes, which is why the steps compose without knowing about each other.
| Workflow | Reads | Writes |
|---|---|---|
| Convert | an ab initio output folder or trajectory | one geometry file |
| Split | one geometry file | per-split geometry files |
| Calc-make | one geometry file | one DFT input folder per sampled frame |
Data can come from aims, cp2k-sp, cp2k-md or orca-sp. Inputs can be
generated for aims, cp2k and orca, with vasp and qe planned.
None of this knows anything about your material. If you want to measure what is actually in a trajectory, tilting angles or RDF or MSD or phonon DOS, that is PeroAna, which reads the same geometry files.
Python 3.10 or newer, because the code uses X | None annotations at runtime.
Depends on ASE, NumPy, PyYAML, joblib and
tqdm.
With uv, which creates the environment for you and
installs the exact versions recorded in uv.lock:
git clone https://github.com/AntonCh-G/Feedstock.git
cd Feedstock
uv sync --extra dev
uv run feedstock --helpuv run uses the project's .venv without you activating anything. If you
prefer an activated shell, source .venv/bin/activate after uv sync.
With pip and an environment you manage yourself:
git clone https://github.com/AntonCh-G/Feedstock.git
cd Feedstock
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"The src/ layout means Feedstock has to be installed before it can be imported.
There is no working-copy fallback, so an uninstalled checkout fails with
ModuleNotFoundError and nothing more helpful than that.
Run the tests with pytest, or uv run pytest under uv. A clean clone runs
fewer of them than your working checkout will, because the suite also checks the
personal configs and templates described below, and those are gitignored.
feedstock run <config.yaml> # execute
feedstock validate <config.yaml> # check the config without runningvalidate catches missing keys, unknown keys and bad values before anything is
read from disk.
Editable examples live in configs/, one or more per workflow,
using /path/to/... placeholders. The configs you actually ran belong in
configs/local/, which is gitignored but still schema-checked by the test suite
so a stale record shows up as a failure rather than a surprise.
templates/ is split the same way: one portable example per DFT
code, and templates/local/ for the ones carrying your allocation ID, your
scratch directory and your email. Both trees are checked, only the first is
committed. ADR-0014 explains
why.
Paths in a config are absolute in every example on purpose. A relative one is
read from wherever you happened to run feedstock, not from the directory the
config sits in.
The config is the record. Nothing else on disk says how a geometry file was produced, so a few habits are worth keeping from the first run rather than the tenth:
- One config per run, saved next to its outputs, named for the step and the
subject —
convert_cp2k_md_300K.yaml, notconfig.yaml. - Once a config has run, don't edit it. A changed parameter is a new file. An edited config describes a run that no longer matches anything on disk.
- Don't reuse
project_namebetween runs. Split writes into<output_dir>/<project_name>/and Calc-make into<output>/<project_name>_<i>/; a reused name either collides or quietly mixes two experiments in one directory. - Keep the terminal output:
feedstock run c.yaml 2>&1 | tee c.log. The retained frame count, the split sizes and the energy shift are printed there and are nowhere in the output files. - Note which version produced the data, from
pip show feedstockorgit -C /path/to/Feedstock rev-parse --short HEAD. Parsers get fixed.
Frames carry part of the provenance themselves: each split records where its frames sat in the input, and every generated input folder records the frame it came from, so a structure traces back to the calculation that produced it.
A CP2K MD run prints its log, positions and forces to three separate files. Convert reads them together:
workflow: convert
source: cp2k-md
input:
log: /path/to/md.log
pos: /path/to/md-pos-1.xyz
frc: /path/to/md-frc-1.xyz
output: /path/to/output.extxyzFor a variable-cell run, NPT and friends, add cell: alongside the other three.
CP2K only logs the initial cell, so without the cell file every frame would
silently carry the wrong one, and you would not find out until the forces looked
strange. Feedstock picks the file up automatically when it sits next to the
positions file. When it doesn't, Feedstock refuses to guess.
Partition into train, validation and test, with energy-shift normalisation:
workflow: split
input: /path/to/input.extxyz
train: 1000
val: 0
test: 4000
energy_shift: true
random_seed: 42Each split records where its frames sat in the input file, so a structure in the training set can be traced back to the calculation that produced it.
Generate FHI-aims input folders from a geometry file and a template:
workflow: calc-make
target: aims
input: /path/to/geometry.xyz
output: /path/to/output_dir/
template: /path/to/Feedstock/templates/aims/pbe_mbd_example
project_name: CsPbI3_ds3916
n_samples: 979A template is a directory of whatever files your code needs, with
__PLACEHOLDER__ tokens where the per-structure values go. Substitution refuses
to leave one unfilled, so a typo in a template is something you find out about
on your laptop rather than after a queue wait.
n_samples draws an energy-stratified subset rather than the first N frames, so
the structures you spend DFT time on span the range the trajectory visited.
Feedstock/
├── src/feedstock/
│ ├── cli.py config.py geometry.py sources.py
│ ├── workflows/ # convert, split, calc_make
│ ├── core/ # DFT parsers, splitting, sampling, file utilities
│ └── inputs/ # Calc-make adapters, one per DFT code
├── tests/ # pytest suite + real-DFT fixtures
├── configs/ # portable example per workflow (local/ = your own, ignored)
├── templates/ # portable DFT template per code (local/ = your own, ignored)
├── docs/adr/ # why the code is shaped this way
├── skills/ # agent skill: what the workflows accept and how to extend them
├── CONTEXT.md # the vocabulary the code and configs share
├── CONTRIBUTING.md # setup, commit convention, what the tests refuse
└── CHANGELOG.md # what each version covers
skills/feedstock/ describes Feedstock for an AI coding
agent: the workflows, every accepted config key, and the contract for adding a
Source, Target or format. It is plain markdown with no tool-specific syntax, so
any assistant can read it, and so can you — REFERENCE.md is the most compact
description of the config format there is.
skills/README.md says how to load it into Claude Code,
Cursor, Copilot, an AGENTS.md-reading agent, or a plain chat window.
It also covers the day-to-day case: an assistant working in your data directory, with Feedstock installed from the checkout. Its Routine use in a data directory section repeats the conventions above, so an agent writes a config and a log even for a one-off rather than inventing its own way of recording what it did.
Three places to add things, each with a small contract:
| Add a… | Do this |
|---|---|
| Source | one parser function in feedstock/core/parsers.py returning list[Atoms] in eV/Å, plus one row in feedstock/sources.py |
| Target | a TargetAdapter subclass in feedstock/inputs/ with write_geometry() and write_input() |
| Format | one _Format row in feedstock/geometry.py |
CONTEXT.md defines the words. The ADR
index says which decision record covers the seam you are
about to touch, and each one is written so you can tell whether the reasoning
still holds before you change anything.
CONTRIBUTING.md covers the rest: how to set up, what the
tests refuse to let you commit, and the commit-message convention.