One binary, ferro, with the subcommands grouped by output rather than by the crate that
implements them.
ferro traj gr | sq | msd | angle | vacf | rotcorr | vanhove | bondlife → stacked csv
ferro map density | velocity | force | radius | sdf | chg-sdf → one .cube per input
ferro net → six stacked csv
+ optional labelled trajectory
ferro bader → ACF / BCF / AVF tables (after Henkelman's)
ferro convert | info → structure files / a summary
ferro build supercell | vacuum | merge | box → one structure file per input
ferro dataset collect | filter | merge → DeePMD system directories
ferro doc <topic> → the manual (compiled into the binary)
The help has three levels: ferro lists the groups; ferro traj lists the commands in that group;
ferro traj gr (without -i) prints that command's parameters, output column structure and examples.
The leaf commands convert / info / bader work the same way: without -i they print their own page.
Two kinds of help coexist and each has its use: the bare command gives the rich page (format lists,
output structure, warning notes), -h gives clap's short parameter table. Options given without
an input (ferro traj gr --dr 0.1) are an error, not a request for the page: the command exits non-zero.
Every command under traj / map / net takes these flags. convert, info and build take only
the four reader flags (--format-in, --units, --type-map, --atom-style); bader and dataset
take none of them and have their own parameters:
| Flag | Description |
|---|---|
-i <FILE>... |
input files; takes several values and expands glob patterns itself (quote them so the shell leaves them alone) |
-o <DIR> |
the output goes into this directory, the current directory by default. When it does not exist ferro asks first ([y/N], on stderr); --mkdir skips the question |
-s <SUFFIX> |
batch marker, the last segment of every product name: <command>[_<table>][_<selection>][_<input>]_<suffix>.<ext> (only a per-frame index of convert / build follows it: traj_700K_0002.cif) |
--mkdir |
create the -o directory without asking. Required in non-interactive environments (scripts, CI), which otherwise error out |
--last-n N |
use only the last N frames (skipping the equilibration stage) |
--ncore N |
number of parallel threads (all cores by default) |
--units <UNITS> |
real (velocity Å/fs, force kcal/mol/Å) or metal (velocity Å/ps, force eV/Å): the LAMMPS unit system of a dump's velocity/force columns. Required whenever a dump has vx/fx-type columns — a dump does not record its units, and the two systems differ by metal). Coordinates and cells are Å under either system; a dump with positions only reads without it |
--type-map <MAP> |
LAMMPS dump without an element or typelabel column: type number → element, e.g. 1=Zr,2=P,3=O. Without it such atoms read as X1, X2, …; a dump that names its elements in either column refuses it |
--atom-style <STYLE> |
atomic, charge or full: the column layout of a LAMMPS data file's Atoms section. Required whenever an input is .data / .lmp / .lammps, checked before the first file is read; see the format notes below |
--format-in <FMT> |
read the input as this format, not by its name (qe/in, vasp/outcar, …; column --format-in of the format table below) |
-i always takes several values; a single input is simply N=1 and gives output of the same shape.
ferro traj gr -i 'runs/*/prod.lammpstrj' -a P -b O -s scanEach input is analysed independently and the results are stacked into one csv with a file column.
Inputs with different element sets take the union of the columns; what is missing stays empty (NaN),
never padded with zeros, never interpolated. A failing input is skipped, leaves its reason in the [inputs] block of the output, and makes the exit code 1 (otherwise an && chain in the shell would take a failure inside the batch for success).
A parameter out of range (--dr 0, --r-min 5 --r-max 3, --nx 0, a step or cutoff of nan or inf,
--angle-max 200, …) is not a per-input failure: it stops the run before the first file is read and
before -o is created, naming the parameter and its allowed range.
Brace expansion {a,b} is not supported; leave that to the shell.
Commands whose output is per-input (the cubes of ferro map, the trajectory of ferro net --export-traj)
are the exception: the input stem has to be part of the file name, otherwise the second input would overwrite the first. -o applies to these two as well.
The file column and those file names use the input's stem. When two inputs share a stem
(runs/700K/prod.lammpstrj, runs/900K/prod.lammpstrj), the second and later become prod_2, prod_3, … in
input order, and ferro prints a FerroNote naming each renamed file. Stems are compared ignoring case
(Prod and prod clash), because the macOS and Windows file systems do.
<-o directory>/<command>[_<table>][_<selection>][_<input>][_<suffix>].<ext>
selection says what was computed and is filled in by the type selection; input names the input
file and appears only in per-input products (ferro map, net --export-traj, bader, build); -s is the batch
marker. The selection comes before the suffix, so ls gr_P-O_* lists the results for one pair across all
batches.
| Command | Where the selection comes from | Example |
|---|---|---|
traj gr |
-a/-b or -x/-y |
gr_P-O.csv, gr_P_3-O_b.csv, gr_all.csv with no selection |
traj angle |
-a/-b/-c or -x/-y/-z |
angle_O-P-O.csv, angle_all.csv with no selection |
traj msd / vacf / vanhove |
--elements, sorted and deduplicated |
msd_O-P.csv, msd_all.csv with no selection |
traj rotcorr |
--center---neighbor |
rotcorr_O-H.csv (both are required, so all is never reached) |
traj bondlife |
--center---neighbor |
bondlife_Si-O.csv and bondlife_events_Si-O.csv |
traj sq |
none (see below) | sq.csv |
map density / velocity / force / radius |
--elements, sorted and deduplicated |
density_Li_<input>.cube, density_all_<input>.cube with no selection |
map sdf / chg-sdf |
--qn as Q<n> |
sdf_P3_Q3_<input>.cube, chg_sdf_Q2.cube |
ferro net |
none | network_qn.csv |
ferro build |
none | supercell_<input>.vasp, merge_<A>_<B>.cif, box.<ext> (vasp unless --format-out) |
Two rules that are easily confused:
gr/anglejoin the parts in the order you wrote them, so-a P -b Oand-a O -b Pland in two files. That is correct:g(r)is symmetric butCNis directed, so the two are genuinely different data.--elementsis sorted before joining, because it is a set:O,PandP,Oselect the same atoms, and one set of data must not end up under two file names.
The selected elements/labels become part of a path, so the character set [A-Za-z0-9_+-] is validated
before the name is assembled, and a violation errors out before the first file is read.
The two groups are mutually exclusive:
| Flag | Meaning |
|---|---|
-a / -b / -c |
select by Atom::element (the element) |
-x / -y / -z |
select by Atom::label (the site label) |
The first slot is the centre (for a pair) or end atom A (for a triplet). The order matters: g(r) is symmetric but CN is directed.
traj sq has no type selection: every pair is always written; see S(q).
Everything ferro says outside its products is one line on stderr, in the layout of Python's warnings:
<source>[:<line>]: <category>: <message>
traj.pdb:57: FerroWarning: last MODEL (frame 2) has no ENDMDL; kept
sets/a.db: FerroNote: no virial; the default stress criterion is off for it
runs/b/prod.lammpstrj: FerroError: trajectory is empty; skipped
ferro: FerroError: 1 failure(s); see the messages above
| Part | Meaning |
|---|---|
| source | the input path as you typed it (a product's path when the message is about the file being written), or ferro for a message about no file in particular |
| line | the line of that file the message is about, when there is one |
FerroNote |
worth knowing; nothing looks wrong |
FerroWarning |
something may be wrong with the input or the result; read it before using the numbers |
FerroError |
failed: one input skipped in a batch, or the whole run stopped (exit code 1) |
stdout carries only the results and progress ([2/5] runs/b/prod.lammpstrj, the tables of info and
dataset), so > log.txt keeps the run's report and 2> messages.txt its messages, and
grep FerroWarning finds every warning of a batch. A message is never split over several lines.
ferro-py prints the same layout; from Python the notes and warnings arrive as ferro.FerroNote /
ferro.FerroWarning (see the Python API page).
Format conversion. -o is a directory (created if missing) and --format-out EXT names the format to
write; both are required. Products are named after the input: traj.lammpstrj with --format-out extxyz
gives DIR/traj.extxyz; an input without an extension (POSCAR) and a DeePMD directory (data.000)
keep their whole name. -s SUFFIX makes it DIR/traj_SUFFIX.extxyz, so same-named inputs from two
runs (two OUTCARs) can share one -o; an existing product is overwritten, with a note. The directory
is created only once there are frames to write, and a -o yet to be made that ends in a format extension
(-o out.extxyz) is refused.
The input format is decided by the file name, unless --format-in names it.
The one exception: a name that says nothing about its format — an OUTCAR, a
vasprun.xml, a CP2K or pw.x .out log — is recognised by the program banner inside, and a
directory holding type.raw is a DeePMD system.
ferro convert # without -i: prints the table below
ferro convert -i input.xyz -o out --format-out pdb # out/input.pdb
ferro convert -i input.cif -o . --format-out vasp # ./input.vasp
ferro convert -i traj.lammpstrj -o out --format-out extxyz --units metal
ferro convert -i traj.lammpstrj -o cifs --format-out cif --units metal # cifs/traj_0000.cif ...| Format | Recognised by | --format-in |
Read | Write | Frames written |
|---|---|---|---|---|---|
| XYZ | .xyz |
xyz |
y | y | all |
| extended XYZ | .extxyz, or a .xyz whose comment line declares Lattice= / Properties= |
extxyz |
y | y | all |
| PDB | .pdb |
pdb |
y | y | all (MODEL records) |
| LAMMPS dump | .dump .lammpstrj |
lammps/dump |
y | y | all |
| CIF | .cif |
cif |
y | y | one per file |
| VASP | .vasp .poscar .pos, or a POSCAR* / CONTCAR* prefix |
vasp/poscar |
y | y | one per file |
| LAMMPS data | .lammps .data .lmp |
lammps/data |
y | y | one per file |
| QE (pw.x) | .in .qe .qein |
qe/in |
y | — | — |
| CP2K input | .inp |
cp2k/inp |
y | — | — |
| CP2K restart | .restart |
cp2k/restart |
y | — | — |
| VASP OUTCAR | banner (an OUTCAR has no extension) |
vasp/outcar |
y | — | — |
| vasprun.xml | banner (.xml) |
vasp/xml |
y | — | — |
| CP2K output | banner (.out), MD or single point by its run type |
cp2k/md cp2k/sp |
y | — | — |
| QE output | banner (.out), pw.x only |
qe/pw |
y | — | — |
| DeePMD | a directory holding type.raw |
deepmd/npy |
y | — | — |
Extensions are compared case-insensitively: CONF.XYZ is XYZ, OUT.CIF is CIF.
--format-in FMT takes the name in the --format-in column and reads the input as that
format, whatever its name and content: ferro convert -i pw.txt -o . --format-in qe/in --format-out cif.
Every command that reads a structure file (info, traj, map, net, build) takes it too.
Things that trip people up:
-
Atoms are written in atomic-number order, in every format. A file listing Zn, Al, P, O is
written as all O, then Al, P, Zn, the way a POSCAR groups its species, so one system converted
from any program comes out in the same order. Atoms of one element keep their relative order,
and forces, velocities and charges move with their atoms. The LAMMPS atom ids and type numbers
follow the new order (
Obecomes type 1). There is no switch to keep the input order. -
The two CP2K input formats and QE input are read-only. To generate either input use
ferro-py job -s cp2k|qe, which writes a complete calculation setup rather than bare coordinates. -
A program output is read as
ferro dataset collectreads it: frames whose SCF did not converge, and an incomplete last step, are dropped, and the counts are printed as a warning. A file with no usable frame is an error. Program outputs and DeePMD systems are read-only too. -
Bytes that are not UTF-8 text are an error naming the file, line, column and byte (a
binary file, or another program's output mixed into a log through
2>&1). Nothing is replaced and read past. -
"One per file" splits the trajectory: 500 frames of
traj.dumpwith--format-out cifgivetraj_0000.cif…traj_0499.cif, the index being the original frame number. CIF is split too, although the format allows severaldata_blocks in one file: VESTA and Materials Studio show only the first block. Thin the frames out with--stride/--number. -
A
.xyzcan be extended XYZ. ASE, GPUMD'strain.xyz, CP2K's EXTXYZ output and ferro's owndataset --type nep|extxyzall write extended XYZ under.xyz, so a.xyzwhose comment line (line 2) declaresLattice=orProperties=is read with the extended XYZ reader; anything else stays plain XYZ. Writing to.xyzstill produces plain XYZ (no cell); write.extxyzto keep the cell. -
CIF. Explicit symmetry operations (
_space_group_symop_operation_xyz, its CIF2 form, or_symmetry_equiv_pos_as_xyz) are used as given; otherwise the Hall symbol, the H-M symbol or the IT number is looked up, and a file with none of them reads as P1. A symbol that fits more than one setting (F d -3 m,C m m e) is an error rather than a guess: append the setting (F d -3 m :2), give_space_group_name_Hall, or list the operations. The monoclinic unique axis and the R-group cell come from the cell parameters; images of one site within$10^{-3}$ (fractional) are one atom, two sites on one position an error. Partial occupancy (_atom_site_occupancy< 1) is refused, naming every such site — order the structure first; an unknown (?) or malformed coordinate is an error naming the site. Blocks without an_atom_site_loop (adata_globalblock) are skipped. A loop whose last row is short of values, or a tag with no value, is an error (as in ASE). Writing a CIF needs a cell; write a non-periodic structure as.xyzor.extxyz. - VASP files often have no extension, so both the prefix and the extension are recognised:
POSCAR,CONTCAR,conf.vasp,conf.poscarandconf.posall go through the same reader/writer pair. -
A LAMMPS data file needs
--atom-style atomic|charge|full. The layout of theAtomssection cannot be read off the file: theAtoms # fullcomment is optional, and the column count is ambiguous (chargeandmolecularboth have 6 columns). Ferro guesses neither, and ignores the comment even when it is present. A line whose column count does not match the style (with or without the three image flags) is an error, as is an atom count different from the header'sN atoms. The header must give all ofxlo xhi,ylo yhiandzlo zhiwith each hi above its lo; a file without its box is incomplete and an error. Image flags are applied,x + ix·a + iy·b + iz·c, as LAMMPSread_dataand ASE do. ferro's own data writer usesfull, so reading its output back takes--atom-style full.
LAMMPS dump with a triclinic cell: the box lines carry LAMMPS's *_bound (the bounding box after
tilting), not xlo/xhi; with all three tilt factors 0 the two forms are identical.
LAMMPS dump with a general triclinic cell (dump_modify triclinic/general yes, header
ITEM: BOX BOUNDS abc origin): the three box lines are the edge vectors *_bound and tilts), rotating positions, velocities and forces into LAMMPS's frame together. In a
dump and a data file alike, a cell skewed past half an edge is written as the same lattice on a reduced
basis, each tilt at most half the edge it leans along: LAMMPS refuses a box skewed further. The atoms
keep their positions, so some may lie outside the box; LAMMPS maps them back in.
Element names in a LAMMPS dump: the element column is used; without it, the typelabel column
(LAMMPS type labels, set with labelmap); with both, element wins. Either is split the same way
when it carries site labels (P_3 → element P, label P_3). With neither, atoms read as X1,
X2, … by type number unless --type-map names them.
LAMMPS output of a structure without a cell (a molecule from .xyz): the box is the atoms' bounding
box widened by 1 Å on every side, and the coordinates are written unchanged, so xlo is generally not 0.
The 1 Å has no physical meaning; it only keeps the box non-degenerate (a planar molecule has zero thickness)
and keeps atoms off the box faces. A dump writes the boundary flags from the frame's periodicity, pp or
ff per direction, so a cell-less frame gets ff ff ff and a slab keeps pp pp ff. A data file has no
boundary flags; set boundary in the LAMMPS input.
Whether velocities and forces survive depends on both sides supporting them: .dump to .xyz silently
drops the velocities, because plain XYZ has nowhere to put them; so do .cif, .pdb and .data (ferro
writes no Velocities section: its numbers would depend on the LAMMPS units, which a data file does not
record). Convert to .extxyz to keep them, or to .dump with --units: in .extxyz they are
written as ASE's momenta column with a masses column next to it, so ASE's get_velocities() returns them.
Extended XYZ numbers are written fixed-point with 10 decimals (ASE writes 8): positions, cell,
forces, energies and stresses keep every digit above
A charge or magnetic-moment column that only some atoms carry is refused (extended XYZ, LAMMPS dump
and data): a structure merged from a charged and an uncharged part would otherwise have the missing values
written as 0, which reads back as "measured 0". --drop-partial leaves the column out instead; LAMMPS data,
whose charge column every atom must have, then writes every charge as 0. A structure in which no atom has a
charge is unaffected: extended XYZ and dumps write no charge column, LAMMPS data writes 0.
ferro convert -i traj.dump -o sub --format-out extxyz --start 100 # skip the relaxation stage
ferro convert -i traj.dump -o sub --format-out extxyz --start 100 --end 199
ferro convert -i traj.dump -o vasp --format-out vasp --stride 50 # one every 50 frames
ferro convert -i traj.dump -o lmp --format-out lmp --number 20 # 20 frames at even intervals| Flag | Default | Description |
|---|---|---|
--start N |
0 |
first frame, 0-based, inclusive |
--end N |
last frame | last frame, 0-based, inclusive |
--stride N |
1 |
take one frame every N within [start, end] |
-N, --number N |
— | take N frames at even intervals within [start, end], both ends included; mutually exclusive with --stride (the same short name as dataset filter) |
Three points of semantics to remember:
- Closed interval, 0-based, matching the frame numbers
ferro infoprints: wheninfoshows the last frame asFrame 4,--end 4reaches it. --strideand--numbercannot be given together; clap rejects that while parsing. One is an interval and the other a total, so the combination states two intentions at once; silently ignoring one of them would be the worse choice.--numberalways includes both ends. The last frame is often the best-equilibrated configuration, and a fixed-stride walk would systematically miss it. When more frames are asked for than exist, each frame is given once; nothing is duplicated to make up the count.
The target format decides; there is no switch:
| Target format | Output |
|---|---|
holds a trajectory (.xyz .extxyz .pdb .dump/.lammpstrj) |
one multi-frame file |
holds a single structure (.cif POSCAR .vasp/.poscar/.pos .lmp/.data/.lammps) |
one file per frame |
The inputs (QE .in/.qe/.qein, CP2K .inp/.restart), the program outputs and DeePMD systems are read-only.
Writing 20 frames as POSCAR can only ever be 20 files, so there is no switch for the user to remember.
The index is inserted before the extension and is the frame index in the original trajectory (not "the n-th one selected"), so the output maps straight back onto the trajectory:
-i traj.dump --format-out vasp --stride 2 → traj_0000.vasp traj_0002.vasp traj_0004.vasp
-i traj.dump --format-out cif --number 3 → traj_0000.cif traj_0002.cif traj_0004.cif
The index is zero-padded to at least 4 digits so that ls sorts by frame order. When only one frame is selected, one file is written with no index, whatever the format.
The index is the one exception to "-s comes last": it follows the tag, -s 700K giving traj_700K_0002.cif,
so that one input's frames stay together in order. List a batch of per-frame files with ls *_700K_*.
-i currently accepts a single file only. Several inputs plus frame selection would make the output
names of different trajectories collide.
The element column carries clean element symbols, with one exception: the LAMMPS dump writer folds
a label of the form <element>_<suffix> (P_3, O_b) into its element column, and the dump reader
splits it back. Labels such as Fe1 are not folded.
Reading a LAMMPS dump, columns are taken by the names on the ITEM: ATOMS line, in any order.
Coordinates come from the first complete set of x y z, xs ys zs, xu yu zu, xsu ysu zsu (ASE's
priority); a dump with none of them is an error rather than atoms at the origin. Scaled columns are
converted by LAMMPS's own definition x reading when the
box does not start at 0). Also read: id (atom order), type, element, mass, q, vx vy vz,
fx fy fz; every other column (mol, ix iy iz, c_*, f_*, v_*, …) is ignored. At least one of
type, element and typelabel must be there, or the dump is an error. A value that
does not parse is an error naming the atom line and column, except on the very last line of the file,
where it is a truncated final frame and is dropped with a warning.
| Flag | Default | Description |
|---|---|---|
-i <file> |
(required) | input file (a single one); omit it to print the format table |
-o <dir> |
(required) | output directory; missing directories are created without asking. Products are named after the input: DIR/<stem>.<EXT>, or DIR/<stem>_0000.<EXT> … when several frames go to a one-structure format |
--format-out <EXT> |
(required) | format to write, as an extension (cif, vasp, extxyz, …) |
--format-in <FMT> |
from the name | read the input as this format (qe/in, vasp/outcar, …; column --format-in above) |
-s, --suffix <SUFFIX> |
— | tag appended to the product name: DIR/<stem>_<SUFFIX>.<EXT> |
--start / --end / --stride / --number |
see above | frame selection |
--drop-partial |
off | leave out a charge / magnetic-moment column only some atoms carry (an error otherwise; see above) |
--units <UNITS> |
— | real or metal: LAMMPS units of dump velocities/forces, on read and on write. Required when they are present |
--type-map <MAP> |
— | LAMMPS dump without an element or typelabel column: type number → element, e.g. 1=Zr,2=P,3=O. Without it such atoms read as X1, X2, …; a dump that names its elements in either column refuses it |
--atom-style <STYLE> |
(none) | atomic / charge / full; required when -i is a LAMMPS data file |
Prints a summary of a structure or trajectory: frame count, element composition, cell parameters, volume
and mass density. The readable formats are exactly those of ferro convert.
ferro info # without -i: prints this page
ferro info -i input.xyz
ferro info -i traj.lammpstrjPer-frame report — the first and the last frame only, not every frame:
| Row | Content |
|---|---|
Atoms |
total count plus the per-element composition |
Cell |
a b c (Å) and α β γ (°); none (non-periodic) for a non-periodic system |
Volume |
ų |
Density |
g/cm³ = Σ(atomic masses) / cell volume. Masses are taken from the file when given explicitly, otherwise from the element table |
PBC |
the periodicity flag per axis |
Energy / Forces / Velocities |
whether the frame carries them |
Two edge cases for the density:
-
Without a cell the row is not printed at all, and no
n/aplaceholder is written — no volume means no density, and a placeholder reads like a measurement. -
An atom with no mass leaves the row out. A symbol that is not in the element table (a stray site label, or a placeholder such as
Xwritten by another program) has no mass unless the file gives one. Ferro does not guess one (no 1 amu stand-in, no readingFe1as Fe): the density row is left out, the rest of the report is printed, an error names how many atoms and which symbols, and the exit code is 1:box.xyz: FerroError: frame 0: no mass for 2 atom(s) (Xx×2): not in the element table and no mass given; density not computedCorrect the element names in the input. The same atoms are an error wherever a mass is needed: extended XYZ
momentaon reading, themasses/momentacolumns on writing, and theMassessection of a LAMMPS data file.
A different volume on the first and the last frame means an NPT trajectory, and the density drifts with it.
For the mean ± σ over the whole trajectory, read # volume = <mean> +/- <std> from the header of any ferro traj output.
Reading a LAMMPS dump that carries site labels prints the element/label split mapping once.
| Flag | Default | Description |
|---|---|---|
-i <file> |
(required) | input file; omit it to print this page |
--units <UNITS> |
— | real or metal: LAMMPS units of dump velocities/forces. Required when they are present |
--type-map <MAP> |
— | LAMMPS dump without an element or typelabel column: type number → element, e.g. 1=Zr,2=P,3=O. Without it such atoms read as X1, X2, …; a dump that names its elements in either column refuses it |
--atom-style <STYLE> |
(none) | atomic / charge / full; required when -i is a LAMMPS data file |
--format-in <FMT> |
from the name | read the input as this format (qe/in, vasp/outcar, …; column --format-in of the format table) |
Trajectory analyses. Each writes one csv for all its inputs (see
Output format conventions); figures are drawn by ferro-py plot, see
Plotting. Parameters, output columns and conventions are on each command's page.
| Command | Computes | Manual |
|---|---|---|
gr |
radial distribution function |
g(r) |
sq |
structure factor |
S(q) |
msd |
mean squared displacement and the self-diffusion coefficient | MSD |
angle |
bond angle distribution | Bond angles |
vacf |
velocity autocorrelation and its Green–Kubo integral | VACF |
rotcorr |
rotational correlation |
Rotational correlation |
bondlife |
intermittent and continuous bond correlations, bonds formed / broken per frame | Bond lifetimes |
vanhove |
van Hove self-correlation |
Van Hove |
3-D spatial distributions in Gaussian cube format. One .cube per input, with no stackable table.
Every name carries its input's name, a single input included (density_all_<input>.cube), so that none
overwrites another and the name does not depend on how many files a glob matched; the selection
(--elements, or Q<n> for sdf) comes before it, so two selections do not overwrite each other
either. -s TAG comes last: density_all_<input>_TAG.cube. All products follow one rule,
<mode>[_<table>][_<selection>][_<input>][_<suffix>].<ext>.
| Command | Computes | Manual |
|---|---|---|
density |
atom number density per voxel | 3-D density maps |
velocity / force
|
mean speed / mean force magnitude per voxel | 3-D density maps |
radius |
hard-sphere occupancy | Hard-sphere occupancy |
sdf |
cluster spatial distribution function around |
Cluster SDF |
chg-sdf |
orientation-averaged charge density around --cubes (QE pp.x cubes) instead of -i and writes one SDF from all of them |
Charge-density SDF |
Glass network topology: --P-O=2.4 (or auto). See
Glass network analysis.
ferro net -i traj.lammpstrj --P-O=2.4 --Al-O=2.4 --Zn-O=2.6 --modifier ZnBader charge decomposition from a DFT charge density. VASP CHGCAR and Gaussian/QE cube are supported.
A cube must be in Bohr (positive voxel counts) and hold one value per grid point; an Å-unit cube
(negative voxel count), a multi-orbital cube, or one whose data count differs from
The Bader implementation is due to be rewritten; until then these are known to be wrong (measured on synthetic densities whose exact partition is known):
-
The default
neargridgives the same result asongrid. Its edge refinement climbs on-grid, so the error (about 0.02 e on a two-peak test) hardly shrinks as the grid is refined.--refine 0skips that refinement and was within$10^{-4}$ e on the same test. -
offgridis wrong. Its interpolated gradient is miscomputed: on a density with two maxima it finds tens of thousands of volumes instead of 2, and the atomic charges are off by 0.1–0.2 e. Do not use it. -
weightloses charge. It drops 0.1–0.3 e without converging, loses charge wholesale (NaN weights) on flat density plateaus, writes the BCF charge column shifted by one volume, and reports one atom's charge as the vacuum charge. Do not use it. -
With a vacuum region (density below
--vacval), the grid methods report a vacuum charge of 0 and a total short of the grid's, and the noise in the vacuum becomes thousands of spurious volumes. - ACF's distance column is the smallest distance from a volume's maximum to its atom, not the distance to the Bader surface, and the layout is not exactly Henkelman's: ASE's and pymatgen's ACF parsers fail on it.
ferro bader # without -i: prints the methods and the output description
ferro bader -i CHGCAR # VASP CHGCAR
ferro bader -i charge.cube # Gaussian/QE cube
ferro bader -i CHGCAR --refine 0 # near-grid without the edge refinement (see Known issues)
ferro bader -i CHGCAR --refine 3 --vacval 1e-4| Flag | Default | Description |
|---|---|---|
-i <file> |
(required) | input (.cube → the cube reader; anything else → the CHGCAR reader) |
-m, --method |
neargrid |
ongrid | neargrid | offgrid | weight |
-r, --refine |
-1 |
edge refinement: -1 automatic, -2 a single pass, N for N passes |
-v, --vacval |
1e-3 |
vacuum density threshold [e/ų] |
-o, --output <DIR> |
next to the input | directory for the three reports |
-s, --suffix <SUFFIX> |
— | tag appended to the report names: bader_ACF_<stem>_<suffix>.dat |
--mkdir |
— | create the output directory without asking (required when there is no terminal) |
| Method | Notes |
|---|---|
neargrid |
the default. Gradient ascent with an accumulated off-grid correction plus edge refinement (as implemented, the refinement makes it equal to ongrid; see Known issues) |
ongrid |
the cheapest, ascending steepest between grid points only. The basin surfaces come out stepped and the charges are systematically a little off |
offgrid |
meant to ascend an interpolated gradient; wrong as implemented (Known issues) |
weight |
after Yu-Trinkle: a grid point is meant to be split by weight across several basins according to the flux, rather than assigned whole; does not conserve charge as implemented (Known issues) |
Three .dat files modelled on the Henkelman group's bader output, bader_<kind>_<input stem>[_<suffix>].dat:
| File | Content |
|---|---|
bader_ACF_<input stem>.dat |
Atomic Charges File — the Bader charge and volume per atom, and a distance column (not the distance to the surface; see Known issues) |
bader_BCF_<input stem>.dat |
Bader Charge File — the charge, volume and coordinates of each Bader volume |
bader_AVF_<input stem>.dat |
Atomic Volume File — the atom → Bader volume index mapping |
The layout is close to, but not exactly, Henkelman's: parsers written for it (ASE, pymatgen) fail on these files.
The three reports are written next to the input file by default (-i run1/CHGCAR →
run1/bader_ACF_CHGCAR.dat), not in the current directory (dataset collect likewise writes beside each
input). VASP charge densities are all called CHGCAR, and only landing next to the input, in
their own run directories, do they avoid overwriting each other. -o <DIR> collects them elsewhere, and -s <SUFFIX> tells two runs with different parameters on the same input apart (bader_ACF_<stem>_<suffix>.dat).
Makes structures: one product per input, <-o dir>/<op>_<input>[_<suffix>].<ext>, in the
input's format unless --format-out names another. It does not take the common flags --last-n /
--ncore.
| Command | Does |
|---|---|
supercell |
replicate a cell, given (--dims) or sized by --min-atoms / --min-length |
vacuum |
add a vacuum layer along one lattice vector |
merge |
stack two structures with a gap, merge_<A>_<B> |
box |
pack molecules at random into a cubic box of a given density, box[_<suffix>].<ext> (vasp by default) |
Parameters and the reasoning behind them: Building structures.
A three-step pipeline for machine-learning training sets. What sets it apart from the other commands:
the output is a directory (a DeePMD system is a directory), so -o is the output root directory;
and it does not take the common flags. The output of filter and merge carries a .train suffix by
default, and the three parts --ratio splits out are .train / .valid / .test; a name already ending
in one of them does not get another. The output of collect carries .db (raw collected data), which is
not a split suffix: filter / merge strip it before naming their own output — md.db filters into md.train, not md.db.train.
All three steps read and write the same directory format, and none of them modifies its own input.
| Command | Does | Manual |
|---|---|---|
collect |
AIMD output → DeePMD system directory (--type inspect for diagnostics only) |
Collecting AIMD output |
filter |
system directory → a filtered system directory | Filtering a dataset |
merge |
several systems → merged by composition | Merging datasets |
Every page of this manual is compiled into the binary via include_str!, so a ferro installed with
cargo install carries it too. The ferro-py pages (python/) are compiled into ferro-py instead and
read with ferro-py doc.
ferro doc # list every topic
ferro doc dataset filter # read one page
ferro doc net > net.md # redirected output is not paged and is a clean fileTopics carry the same names as the subcommand tree (dataset filter, traj gr, net), so the
Full documentation: line at the end of every help page is the next command to type rather than a path to
go looking for. Pages that do not correspond to a single command use flat names (data-model,
installation, …); ferro doc with no argument lists every one. Short forms such as gr, filter and network have aliases.
convert / info / bader have no page of their own — they are sections of this page, and ferro doc
addresses sections, taking that ## heading up to the next one of the same level, so it prints a few dozen lines rather than the whole book.
Redirected or piped, the markdown is printed exactly as written — ferro doc net > net.md is the
source file, byte for byte. On a terminal it is rendered first and then goes through $PAGER
(less -R by default), the behaviour of git and man. A missing pager, or one that will not start,
falls back to printing rather than failing.
Rendering reflows prose to the terminal width (at most 100 columns), draws tables with borders and wraps
their cells to fit, and turns inline formulas such as $\alpha_1$ into α₁. A sub- or superscript
that has no Unicode form stays as written (τ_c, r_{min}), and so do display formulas ($$...$$) and
any command the renderer does not know. The width comes from the terminal, then $COLUMNS, then 80.
On Windows, wherever NO_COLOR is set to a non-empty value, and under TERM=dumb, the output is plainer:
Windows gets ASCII only (no colour, +--+ table borders, formulas left as TeX), because an older console
may show neither escape codes nor box-drawing characters; NO_COLOR and TERM=dumb turn off colour only.
Apart from ferro map (cube) and ferro bader (ACF/BCF/AVF), every output is one csv with a #
comment block above the data holding the shared parameters and the [inputs] list (frame count, atom
count, volume and status per input). Several inputs stack into that one table, told apart by a file
column.
That block is for people to read — pandas.read_csv(comment="#") drops it, so anything a script has to
parse is always a column and is never hidden in a comment.
Numbers are formatted uniformly as {:.6e}, and NaN renders as an empty field (under a column union, a column an input lacks is empty, not 0).