Skip to content

Latest commit

 

History

History
623 lines (494 loc) · 39.8 KB

cli-reference.md

File metadata and controls

623 lines (494 loc) · 39.8 KB

CLI Reference

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.


Common Flags

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 $10^3$ in velocity and ~23 in force, so the value is never guessed (DeePMD runs in 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)

Batch processing

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

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

Output naming

<-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 / angle join the parts in the order you wrote them, so -a P -b O and -a O -b P land in two files. That is correct: g(r) is symmetric but CN is directed, so the two are genuinely different data.
  • --elements is sorted before joining, because it is a set: O,P and P,O select 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.

Type selection (gr / angle)

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


Messages

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


ferro convert

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 (O becomes 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 collect reads 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.dump with --format-out cif give traj_0000.cif … traj_0499.cif, the index being the original frame number. CIF is split too, although the format allows several data_ blocks in one file: VESTA and Materials Studio show only the first block. Thin the frames out with --stride / --number.
  • A .xyz can be extended XYZ. ASE, GPUMD's train.xyz, CP2K's EXTXYZ output and ferro's own dataset --type nep|extxyz all write extended XYZ under .xyz, so a .xyz whose comment line (line 2) declares Lattice= or Properties= is read with the extended XYZ reader; anything else stays plain XYZ. Writing to .xyz still produces plain XYZ (no cell); write .extxyz to 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 (a data_global block) 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 .xyz or .extxyz.
  • VASP files often have no extension, so both the prefix and the extension are recognised: POSCAR, CONTCAR, conf.vasp, conf.poscar and conf.pos all go through the same reader/writer pair.
  • A LAMMPS data file needs --atom-style atomic|charge|full. The layout of the Atoms section cannot be read off the file: the Atoms # full comment is optional, and the column count is ambiguous (charge and molecular both 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's N atoms. The header must give all of xlo xhi, ylo yhi and zlo zhi with 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 LAMMPS read_data and ASE do. ferro's own data writer uses full, 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 $\mathbf{A}$, $\mathbf{B}$, $\mathbf{C}$, each followed by one component of the origin, and they are read as such; positions, velocities and forces are taken as written, already in that frame. ferro writes the restricted form (*_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 $10^{-10}$, far below DFT precision. Values smaller than that, and the last bits of a float64, do not survive a round trip.

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.

Frame selection

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 info prints: when info shows the last frame as Frame 4, --end 4 reaches it.
  • --stride and --number cannot 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.
  • --number always 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.

One file or N files

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 = x_{lo} + s \cdot L$, so an atom reads the same whichever kind was dumped (ASE leaves out $x_{lo}$ for scaled columns and so disagrees with its own 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

ferro info

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

Per-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/a placeholder 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 X written by another program) has no mass unless the file gives one. Ferro does not guess one (no 1 amu stand-in, no reading Fe1 as 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 computed
    

    Correct the element names in the input. The same atoms are an error wherever a mass is needed: extended XYZ momenta on reading, the masses / momenta columns on writing, and the Masses section 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)

ferro traj

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)$ and coordination number $CN(r)$ g(r)
sq structure factor $S(q)$, X-ray and neutron weighted 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 $C_1(t)$ / $C_2(t)$ of an orientation vector Rotational correlation
bondlife intermittent and continuous bond correlations, bonds formed / broken per frame Bond lifetimes
vanhove van Hove self-correlation $G_s(r, \tau)$ Van Hove

ferro map

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 $Q^n$ clusters Cluster SDF
chg-sdf orientation-averaged charge density around $Q^n$ clusters; takes --cubes (QE pp.x cubes) instead of -i and writes one SDF from all of them Charge-density SDF

ferro net

Glass network topology: $Q^n$ of the network formers, ligand types, coordination numbers and linkages, written as six stacked csv; cutoffs are given per pair as --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 Zn

ferro bader

Bader 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 $N_1 N_2 N_3$ is refused rather than read as a shifted or empty grid. An orbital cube (negative atom count) with a single orbital is read, skipping its orbital list.

Known issues

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 neargrid gives the same result as ongrid. 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 0 skips that refinement and was within $10^{-4}$ e on the same test.
  • offgrid is 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.
  • weight loses 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)

Choosing among the four methods

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)

Output files

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


ferro build

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.

ferro dataset

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

ferro doc

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 file

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


Output format conventions

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