ferric recipes

Copy-paste workflows that end in a number. Each gives its expected output and rough runtime, so you can tell "still running" from "silently wrong".

Outputs for recipes 1 and 3 come from running them as written (2026-09-23); recipe 2's output is from an earlier run. MEASURED marks numbers that came out of the program.

Prerequisite: a working ferric. The fast path is the prebuilt wheel (about a minute). A source build takes about 10 minutes, mostly compiling ferric:

pip install ferric

See Installation. Build from source only if you are changing ferric itself or need MPI.

Which recipes need a git clone: the wheel contains the compiled library and the ferric command, nothing else. examples/, testdata/ and scripts/ live in the repository (what the wheel does not contain). Run those recipes from the repository root:

RecipeNeeds a clone?Extra dependencies
1. Single pointonly for examples/water-rhf.toml; your own .xyz + TOML works anywhere—
2. Ions and radicalsno—
3. Optimizeonly for examples/h2-lda-opt.toml—

On a shared machine, run anything real under a memory cap. ferric's memory budget charges only the large, size-dependent tensors, so a job's resident memory can exceed it. Only a cgroup puts a hard ceiling on the process; without one, an overshoot can trigger the system-wide OOM killer, which may kill unrelated processes. Details are in Sharp bits and For agents. scripts/ferric-limited (repository, Linux with systemd) wraps a command in a systemd-run --user scope:

scripts/ferric-limited --max=8G --high=7G -- ferric input.toml

1. Single-point energy from a TOML file

The first success that confirms your install works. From the repository root:

ferric examples/water-rhf.toml

MEASURED (about 2 s including start-up):

RHF/sto-3g on testdata/molecules/water.xyz
  nbasis     = 7
  iterations = 9
  converged  = true
  energy     = -74.9631468000 Hartree

From a source checkout, the same run is cargo run --release --bin ferric -- examples/water-rhf.toml.

For your own molecule you need an .xyz (Å) and a TOML file:

[molecule]
xyz = "mymol.xyz"        # relative to the directory you run ferric from

[basis]
name = "def2-svp"

[method]
kind = "rhf"

For every method.kind, see Capabilities and validation. For every TOML key, see the input reference.


2. Charged and open-shell species

Read this before running any ion, radical or metal center. charge and multiplicity are [molecule] keys. Most files in examples/ leave them at their defaults (0 and 1). The open-shell exceptions are examples/h_uhf.toml and examples/oh-ugw.toml.

[molecule]
xyz = "tBu.xyz"
charge = 1          # cation
multiplicity = 1    # closed-shell singlet

[basis]
name = "def2-svp"

[method]
kind = "ksdft"

[dft]
functional = "B3LYP"

Relax the geometry with xtb first. A correct formula doesn't guarantee a physical structure, and the parity check below can't tell the difference. MEASURED: a hand-built C10H17+ with a 0.902 Å C–H contact stalled the SCF. After xtb mol.xyz --opt --chrg 1 --uhf 0 --gfn 2 it converged, 545 kcal/mol lower. See Golden paths Step 1b.

MEASURED on the tert-butyl cation C4H9+ (13 atoms), B3LYP/def2-SVP:

converged  = true
energy     = -157.4301362681 Hartree

Expect seconds to about a minute at this size.

The error you will hit first

If the electron count and the multiplicity disagree, you get this error:

error: inconsistent charge/multiplicity: 35 electrons with multiplicity 1
implies n_alpha = (35 + 1 - 1) / 2 = 35/2, which is not a non-negative
integer... An odd electron count needs an even multiplicity (2, 4, ...)
and vice versa

It means your geometry, your charge or your multiplicity is wrong. ferric can handle ions and open shells. The message prints the arithmetic and the rule, so check the atom count in your .xyz against the first line of the file, then the charge and multiplicity you set.

A radical (open-shell doublet) uses the same keys:

[molecule]
xyz = "radical.xyz"
charge = 0
multiplicity = 2    # one unpaired electron -> UHF/UKS

From Python, multiplicity belongs to the molecule, not to the run_* call: ferric.Molecule.from_xyz("x.xyz", charge=0, multiplicity=2).


3. Optimize a geometry

ferric examples/h2-lda-opt.toml

MEASURED (about 2 s): converged = true, steps = 2, final E = -1.1212649781 Hartree.

The pattern is task = "optimize" next to any kind that has analytic gradients:

[method]
kind = "ksdft"
task = "optimize"

[dft]
functional = "B3LYP"

[optimize]
max_steps = 30

Capabilities and validation lists which methods have analytic gradients. Harmonic frequencies use task = "frequencies" (the analytic Hessian where it applies, otherwise finite differences of the analytic gradient; see examples/water-frequencies.toml).

Runtime depends on the system, so measure before you plan. A whole optimization at drug-like size has not been timed on this page. For scale, two single-point measurements:

  • 32 atoms, PBE/def2-SVP: 96 s.
  • A 27-atom delocalised cation, PBE/6-31G: 4.9 min, because it needed 173 SCF iterations. At B3LYP/def2-SVP the same molecule was killed for memory after 22 min (Golden paths, Step 2).

An optimization multiplies the single-point cost by the number of steps. Measure one species before you queue many.


4. Comparing against another code

Three causes account for most spurious "ferric disagrees" reports:

Density fitting. ferric's KS-DFT (kind = "ksdft", run_dft) fits the Coulomb term by default, and the fitting error grows with system size. MEASURED at PBE/STO-3G against exact Coulomb: water 0.28, benzene 1.16, and a 71-atom drug molecule 9.5 kcal/mol. Compare like with like. run_dft(..., df_j_aux="exact") turns fitting off, or you can turn it on in the other code.

Grid. ferric's KS-DFT default grid is (75, 110) (radial, angular), flat, with no pruning. PySCF's default grid.level=3 is roughly (75, 302). The difference is worth ~1e-5 Ha on water and grows with atom count, because grid error scales with the number of atoms, not the basis size. A small basis on a big molecule can be worse than a big basis on a small one. Match grids before you conclude anything.

Convergence criteria. Setting energy_conv alone can leave the density loosely converged. Variational quantities (E_HF) don't mind. Anything that depends linearly on the MO coefficients (correlation energies, properties) does. density_conv is the criterion that converges the density, so tighten it when you care about those quantities. Keep energy_conv loose: with density fitting, a very tight energy_conv may be unreachable (Golden paths, Step 2).

[scf]
density_conv = 1e-9