Skip to content

Repository files navigation

Feedstock

tests Python License

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.

Why

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.

How it works

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.

Install

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 --help

uv 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.

Usage

feedstock run <config.yaml>       # execute
feedstock validate <config.yaml>  # check the config without running

validate 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.

Keeping a run reproducible

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, not config.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_name between 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 feedstock or git -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.

Convert

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.extxyz

For 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.

Split

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: 42

Each 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.

Calc-make

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: 979

A 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.

Project layout

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

Working with a coding agent

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.

Extending

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.

About

Turn ab initio output into geometry files for machine-learning force fields

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages