ferric

A quantum chemistry engine written in Rust, with Python bindings and a TOML-driven command line. It computes Hartree–Fock and DFT energies and gradients, the MP2 family, coupled cluster, RPA and GW, and constrained DFT, with libint2 for the integrals.

pip install ferric      # Linux x86_64, Python 3.10–3.13

Start here

Get started → Install the wheel and compute a checked number in under a minute. Then read the sharp bits that catch new users.

How-to guides → Task recipes: charged and open-shell molecules, optimization, QM/MM. Choosing a method maps a chemistry question to a method.

Methods → What each method family is, how to run it, how accurate it is, what it costs and what to cite.

Reference → Capabilities and validation · Input file · Python API · Examples · Rust API

If you areStart with
A chemist who wants numbersYour first calculation, then Choosing a method
Coming from PySCFFor PySCF users
Working on drug-discovery workflowsEnd-to-end applications and QM/MM
A method developerElectronic response, Architecture, Rust API
An automated agentFor automated agents

Implemented ≠ validated

Working code is not a checked number. These pages describe what exists; how far each capability's numbers have been checked against an independent reference differs a lot between methods. Each one is graded individually in Capabilities and validation. Read it before relying on a result.

The idea

ferric is organized around electronic response: how the density reacts to a perturbation. That object appears as the polarizability \( \alpha \), the dielectric function \( \varepsilon \) and the susceptibility \( \chi \), and three of the method families here are different ways of getting it right where standard methods get it wrong:

  • Attenuated MP2: MP2 builds dispersion from an uncoupled polarizability, which overbinds some systems (π-stacking is the classic case). Attenuating the correlation operator removes the long-range part where that error lives.
  • PDEP-RPA / GW: the dielectric matrix is the density–density response. PDEP keeps only its dominant eigenmodes, a low-rank representation of the screening used by RPA correlation and the GW interaction.
  • Constrained DFT: a constraint couples to the density and reads its response, building charge-localized diabatic states and their electron-transfer couplings.

Electronic response develops the argument and says which parts of it are demonstrated and which remain a design premise.

Source, license, citation

github.com/mgoldey/ferric, dual-licensed MIT / Apache-2.0. To cite ferric and the methods you used, see References and citing.

Installation

There are two ways in. Most people want the first.

You want toDo thisTime
Run calculationsInstall the prebuilt wheelabout a minute
Change ferric's Rust code, or use MPIBuild from sourceabout 10 minutes, mostly compiling ferric

Fastest: the prebuilt wheel

Wheels are published to PyPI for Linux x86_64 (manylinux_2_28), CPython 3.10–3.13, and for no other platform:

PlatformWheelSource build
Linux x86_64, CPython 3.10–3.13yesyes (the only tested platform)
Linux aarch64nonot tested
macOS (x86_64, arm64)not yet (wanted; no wheel is built)not tested
Windowsnonot supported: build.rs hardcodes Unix paths and links Unix static archives

libint2 and libxc are statically linked into the extension, and OpenBLAS ships inside the wheel as a bundled shared library, so nothing needs compiling. The wheel's libint2 carries second derivatives, so analytic Hessians work from a plain pip install; see What the libint2 build carries.

pip install ferric        # or: uv pip install ferric

The only releases so far are pre-releases (0.1.0rc*). pip and uv both select them when no final release exists, so the plain command above works. Pin a version (ferric==0.1.0rc5) if you need a reproducible environment. Which build am I running shows how to check.

The wheel gives you two things:

  • the Python module: import ferric
  • a ferric command on PATH that runs TOML input files (the same CLI as cargo run --bin ferric)

Check it works

python -c "import ferric; print(ferric.__file__)"

Then run your first calculation. It takes under a second.

What the wheel does not contain

The wheel holds the compiled library and nothing else. The examples/ input files and the testdata/ molecules live in the git repository. Pages that use them say so. To have them locally:

git clone https://github.com/mgoldey/ferric
cd ferric
ferric examples/water-rhf.toml

The CLI resolves [molecule] xyz = "..." relative to the current directory, so run example files from the repository root.

The large-libint wheel (GitHub releases)

Starting with 0.1.0rc8, each GitHub release also carries a second wheel for each CPython. It has the same version and commit as the PyPI wheel, but it is built against conda-forge's libint2 2.13.1 (the build scripts/install-libint.sh installs), which it bundles as a shared library. Its file name carries the wheel build tag 1largeam:

ferric-0.1.0rc8-1largeam-cp311-cp311-manylinux_2_28_x86_64.whl

Compared with the PyPI wheel it adds l = 7 shells, analytic 3- and 2-centre second derivatives, multipoles above order 2 and G12 (F12) integrals; see What the libint2 build carries. It does not extend analytic Hessians: 4-centre second derivatives stop at f functions in both wheels. RI-J/RI-K Hessians already work with the PyPI wheel, so they are no reason to switch. Nor is it a strict superset: its one-electron integrals reach l = 7 / 6 / 3 (energy / 1st / 2nd derivative), while the PyPI wheel's reach 6 / 6 / 6, so one-electron second derivatives of g, h or i functions are available only in the PyPI wheel.

Install the PyPI wheel first, so its dependencies are present, then swap in the file for your Python from the release (check it against the release's SHA256SUMS):

pip install ferric==0.1.0rc8
pip install --force-reinstall --no-deps \
  https://github.com/mgoldey/ferric/releases/download/v0.1.0rc8/ferric-0.1.0rc8-1largeam-cp311-cp311-manylinux_2_28_x86_64.whl

Both wheels are the distribution ferric at the same version, so pip list shows no difference and only one can be installed at a time:

  • pip install ferric==0.1.0rc8 then reports "already satisfied" and keeps the large wheel.
  • pip install -U ferric at the next release replaces it with that release's PyPI wheel; swap in the large one again from the new release.
  • With both files on one --find-links index, pip picks the build-tagged (large) wheel.

To see which one is loaded:

python -c "import ferric; i = ferric.build_info(); print(i['libint_version'], i['libint_linkage'], i['libint_max_am'])"

The large wheel prints 2.13.1 shared and 'eri3': [7, 7, 4]; the PyPI wheel prints 2.7.2 static and 'eri3': [6, 6]. The libint: line of ferric --version reports the same limits.

The large wheel contains a libint2 binary (LGPL-3.0-only) built by conda-forge. Its source is the loriab/libint v0.1 generated tarball, built by the conda-forge libint feedstock; each release's notes link both and give the tarball's sha256. The licence texts ship with the release as libint2-LICENSE.txt and libint2-COPYING.LESSER.txt, covered by its SHA256SUMS.

Building from source

Build from source if you are changing ferric, need the MPI build, or are on a platform without a wheel (only Linux x86_64 is tested; see the table above). ferric links libint2, a C++ integral library; scripts/install-libint.sh installs a prebuilt copy in seconds.

Prerequisites

  • Rust 1.75+: install via rustup
  • libint2 2.13.1: the conda-forge Linux x86-64 build, which includes second-derivative integrals; scripts/install-libint.sh downloads it (checksum pinned) and needs curl, python3, zstd and patchelf
  • libxc, OpenBLAS, LAPACK, Eigen3 and Boost headers
  • Python 3.10+ and maturin: optional, for the Python bindings

Steps (Ubuntu 22.04+)

The apt list matches what CI installs (.github/workflows/ci.yml).

# 1. System dependencies
sudo apt-get install -y build-essential cmake g++ gfortran wget git \
    libeigen3-dev libopenblas-dev liblapack-dev pkg-config \
    libxc-dev libboost-dev patchelf zstd \
    python3-dev python3-pip python3-venv

# 2. Get ferric and install libint2 (seconds)
git clone https://github.com/mgoldey/ferric
cd ferric
scripts/install-libint.sh ~/.local/libint2-2.13.1
export LIBINT2_PREFIX=~/.local/libint2-2.13.1

# 3. Build ferric
cargo build --release

# 4. Check it: seconds, not minutes
OPENBLAS_NUM_THREADS=1 ./target/release/ferric examples/water-rhf.toml

The last line should end with converged = true and energy = -74.9631468000 Hartree, as shown in the first calculation.

LIBINT2_PREFIX is read by crates/ferric-integrals/build.rs; unset, it defaults to the installer's ~/.local/libint2-2.13.1 when that exists, and to ~/.local otherwise, so the export above is optional for the default path. The install script records the library's absolute path in every binary that links it, so moving or deleting that directory breaks the build's executables until they are rebuilt. A prefix holding a static libint2.a (a from-source libint2 build) also works, but a library generated without second derivatives has no analytic Hessians; frequencies then use finite differences of the analytic gradient.

The workspace has three binaries (ferric, ferric-cli and ferric-batch), so cargo run needs to be told which one: cargo run --release --bin ferric -- input.toml.

Running the test suite

OPENBLAS_NUM_THREADS=1 cargo test --workspace

.cargo/config.toml sets OPENBLAS_NUM_THREADS=1 for cargo-invoked processes only when the variable is unset (its [env] entry has no force = true, so an exported value wins). The prefix above makes this command use 1 regardless of your shell. Do not raise it above 1; see Threading. The Python binding tests are pytest, not cargo; see CONTRIBUTING.md.

Debug vs release

  • Debug (cargo build): fast to compile, slow to run. Use it while iterating on Rust code. It catches debug_assert! violations and integer overflow that release builds silently allow.
  • Release (cargo build --release): slow to compile, fast to run. Use it for anything you will wait on: real molecules, benchmarks, and any RPA/GW/CC job.

Python bindings from source

uv sync
uv run maturin develop --release
uv run python -c "import ferric; print(ferric.__file__)"

Use uv run maturin develop, not a bare maturin develop. A bare one can install into a different interpreter from the one uv run python loads, and the stale build keeps getting imported.

Building from the sdist

Each release publishes an sdist next to the wheels, so pip download ferric==<version> --no-binary :all: succeeds. The sdist holds the Rust workspace crates the extension depends on, Cargo.lock and the vendored libecpint. It does not hold libint2, so it is not a self-contained build: pip install from it needs everything in Prerequisites already present, with LIBINT2_PREFIX set to a libint2 install. The libint2 source is generated and too large to bundle (the export below is 183 MiB against PyPI's 100 MiB file limit).

Two ways to get a libint2 for an sdist build:

  • Linux x86-64: scripts/install-libint.sh (conda-forge 2.13.1). The script is not in the sdist; take it from the repository.
  • Any platform with CMake and Eigen3: build the export the wheels use, libint-2.7.2-ferric-small-1.tgz from the libint-2.7.2-ferric-small-1 release (verify its sha256 against LIBINT_SHA256 in .github/workflows/wheels.yml). Configure it with cmake .. -DCMAKE_INSTALL_PREFIX=$PREFIX -DCMAKE_PREFIX_PATH=$PREFIX -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release, then make and make install, and set LIBINT2_PREFIX=$PREFIX. The build takes about 30 minutes. It has the wheel's integral classes (see the table below), so F12 methods still need the 2.13.1 build. The recipe is tested only as part of the Linux x86-64 wheel build.

On a platform with no wheel, plain pip install ferric falls back to this sdist and fails at the libint2 step unless those prerequisites are present. An sdist build carries no git metadata, so the build identity reports commit: "unknown".

What the libint2 build carries

Integral classes, derivative orders and angular-momentum limits are fixed when libint2's source is generated, so no build flag changes them. Highest angular momentum for energy / 1st / 2nd derivatives (– = not generated):

IntegralsSource build with scripts/install-libint.sh (conda-forge 2.13.1)PyPI wheel (ferric's libint2 2.7.2 export)Needed for
4-centre ERI7 / 6 / 36 / 6 / 3SCF, gradients, analytic Hessians
One-electron (overlap, kinetic, nuclear)7 / 6 / 36 / 6 / 6the same
3- and 2-centre ERI (auxiliary shell)7 / 7 / 46 / 6 / –RI-MP2, RPA, GW and their gradients
G12 geminal4 / – / ––F12 / geminal integrals

The 3-centre limit is for the auxiliary shell; libint caps the two orbital shells of a 3-centre integral at its default limit for that derivative order (the wheel: l ≤ 6 for energies, l ≤ 4 for first derivatives; 2.13.1: 6 / 6 / 3).

With either build, analytic Hessians cover orbital bases up to f functions; a basis with g or higher functions uses finite differences of the analytic gradient. The wheel has no G12 class, so F12 methods need a source build. scripts/generate-libint-small.sh regenerates the wheel's export. The upstream mpqc4 tarball (libint2 2.7.2) has no second derivatives and no G12 class: built against it, ferric uses finite-difference Hessians and the G12 tests skip with an explicit message.

Which build am I running

python -c "import ferric; print(ferric.__version__); print(ferric.build_info()); print(ferric.__build__)"
ferric --version

The wheel and a conda-forge 2.13.1 build share a version and commit; they differ in the libint2 limits in the table above, which ferric --version (the libint: line) and ferric.build_info()["libint_max_am"] report.

ferric.build_info() and ferric --version report the version, the full git commit, whether tracked files differed from that commit (dirty), the cargo profile and the libint2 version. ferric.__build__ is the compact stamp {"git_sha": ..., "dirty": ...}. All of it is fixed when the extension is compiled, so it describes the loaded .so even if that is a symlink into another checkout.

  • A released wheel carries the plain version (0.1.0), the tagged commit and dirty: False. To release, push a vX.Y.Z tag; there is nothing to commit, and the build fails rather than ship from a dirty or unidentified tree.
  • A build from a checkout carries a .devN version (0.1.0.dev0) and the commit it was built from, with dirty: True if tracked files had uncommitted changes. Untracked files do not count.
  • A build with no git metadata (an sdist or source tarball) reports commit: "unknown", and ferric.__build__ is None.

MPI

MPI is a source build only. The wheels workflow publishes no MPI wheel to PyPI.

sudo apt-get install -y libopenmpi-dev openmpi-bin libclang-dev
cargo build --release --workspace --features mpi
mpirun -np 4 -x OPENBLAS_NUM_THREADS=1 ./target/release/ferric examples/water-rhf.toml

libclang-dev is needed by mpi-sys's bindgen step. MPICH and Intel MPI are not supported: their libmpi.so.12 ABI differs from OpenMPI 4.x's libmpi.so.40.

The CLI is the MPI entry point; the Python API is not

The CLI is SPMD by design: mpirun -np N ferric input.toml runs one rank per process.

mpirun -np N python script.py is not supported. The bindings expose no rank or world-size accessor, so if rank == 0 cannot be written. Every rank runs the whole script, prints N times, and races on the same output files.

GPU (CUDA)

The CUDA backend is a source build only and is off by default even when built in.

cargo build --release -p ferric-cli --features ferric-cli/gpu
FERRIC_GPU=auto ./target/release/ferric examples/water-ccsd.toml

Building needs no CUDA toolkit: the driver and cuBLAS libraries are loaded at run time. Running needs an NVIDIA driver and the CUDA 12 runtime libraries (libcuda.so.1, libcublas.so.12) on the loader path; without them mode = "auto" prints a notice and runs on the CPU, and mode = "on" stops the ferric binary with the reason. Library and Python callers that set FERRIC_GPU=on with no usable device get the same notice and a CPU run, not an error; check ferric.gpu_status(). The CUDA 12 libraries support compute capability 5.0 and above; CUDA 13 does not support Pascal (compute capability 6.x), so a Pascal card needs the CUDA 12 libraries.

What runs on the device, under mode = "auto" or "on":

  • Dense f64 contractions issued through einsum! (the coupled-cluster and MP2-family drivers) above a size threshold.
  • The closed-shell RI-MP2 energy, with B_ov resident on the card: f64 by default ([precision] mode = "f64"), or with [precision] mode = "mixed" B_ov stored as f32 and accumulated in f64 (see the validation page for its measured error).
  • The unrestricted RI-MP2 energy (open-shell reference, amplitude-free path), with one resident B_ov per spin: f64 by default, or with precision = "mixed" each spin's B_ov stored as f32 and the αα, ββ and αβ blocks accumulated in f64 by the same kernel.
  • Mixed applies to the RI-MP2 energy under the Coulomb, erfc and terfc operators, closed-shell or unrestricted (plain RI-MP2, attenuated RI-MP2, SCS-MP2 with two terfc operators, the MP2 half of att-MP2+VV10); the erf operator, composite fitted operators, SR-MP2 in RS-MP2+RPA, OO-MP2 and kappa-regularised runs use the f64 device kernel.
  • The density-fitted Coulomb matrix (RI-J): the raw three-index tensor is stored on the card once per geometry (packed lower triangle, f64) and each J build moves only the density weights and the result.
  • The density-fitted exchange matrix (DF-K, occupied-orbital path): the dressed three-index tensor is stored on the card once per geometry and each K build moves only the occupied coefficients and the result, in f64.
  • The Kohn-Sham exchange-correlation grid (closed- and open-shell, LDA, GGA and meta-GGA), only under mode = "on" (or a preset that sets it): auto keeps the XC grid on the CPU, because the device path was measured slower than the CPU. Measured on a GTX 1080 (Pascal, sm_61), PBE/cc-pVDZ SCF wall time: benzene 2.0 s on the CPU, 3.6 s with the device XC grid in f64 and 5.5-6.8 s in mixed; n-octane 8.5-8.9 s, 18-23 s and 17 s; each device XC build took 2.2-3x the host time (6-core host, RAYON_NUM_THREADS=3). With mode = "on" the screened per-batch basis-function values are stored on the card once per grid, and each Fock build runs the two per-batch matrix products (density Φ = D·χ and potential V = A·χᵀ) on the device in f64; naming xc-vxc in mixed_kernels (with precision = "mixed") forms the potential product from f32 operands with f64 accumulation while the SCF density is still changing (opt-in; it is not in the default mixed set). Grid construction, screening, the libxc functional evaluation, the density reductions and the fixed-order V accumulation stay on the CPU, so the result is reproducible and independent of the thread count. The VV10 nonlocal grid and XC gradients run on the CPU.

RI-J and DF-K run on the CPU when their tensor does not fit the device pool (or, for DF-K, is spilled or recomputed on the host) and under multi-rank MPI; the notice names the reason. When one SCF has both and the pool cannot hold both, DF-K gets the device. The XC grid runs on the CPU when its basis-function blocks do not fit what is left of the device pool, or after any device error (the notice names the reason; the failed pass is recomputed on the CPU). Integrals, SCF diagonalisation, DIIS and grid construction run on the CPU. Device results are reproducible run to run on one device and agree with the CPU to the accuracy of a different summation order, not bit for bit.

The shortest way to use the device is one line in the input, gpu = "on" (f64) or gpu = "mixed", or ferric --gpu mixed input.toml on the command line (the flag overrides the input file and FERRIC_GPU_PRESET). The [gpu] table in the input reference lists the keys and what each preset turns on.

Threading

The ferric binary and import ferric pin OpenBLAS to one thread when OPENBLAS_NUM_THREADS is unset, and honour the variable when it is set (cargo sets it to 1 through .cargo/config.toml, but only when it is unset, so an exported value reaches cargo-launched processes). Leave it unset or at 1. ferric uses rayon for its parallelism; a threaded OpenBLAS on top of that oversubscribes the machine, and its LU routines can crash when called from rayon workers. For throughput across many independent jobs, prefer many single-threaded processes over one multi-threaded job.

Your first calculation

This page takes you from an installed ferric to one checked number, then shows where to go next. It assumes you have run pip install ferric (Installation). No git clone is needed until the last section.

We compute the Hartree–Fock energy of water in the minimal STO-3G basis. It takes well under a second and has a known answer, so you can tell immediately whether your installation is right.

1. From Python

Save this as water.py:

import ferric

# XYZ format: atom count, a comment line, then symbol x y z in Ångström.
water = ferric.Molecule.from_xyz_string("""3
water
O   0.000000   0.000000   0.117790
H   0.000000   0.755453  -0.471161
H   0.000000  -0.755453  -0.471161
""", 0, 1)                       # charge 0, spin multiplicity 1 (singlet)

basis = ferric.BasisSet.bundled("sto-3g")
rhf = ferric.run_rhf(water, basis)

print(rhf.converged, f"{rhf.energy:.10f}")

Run it:

OPENBLAS_NUM_THREADS=1 python water.py

You should see:

True -74.9631468000

Two things to notice:

  • Always read converged. When run_rhf runs out of iterations it still returns an energy; it sets the flag and does not raise. A number with converged == False is not a result. See Sharp bits.
  • Energies are in Hartree. Geometries go in as Ångström; see Sharp bits for which accessors return Bohr.

2. The same thing from the command line

The wheel also installs a ferric command that reads a TOML input file. Write the geometry to water.xyz:

printf '3\nwater\nO 0.000000 0.000000 0.117790\nH 0.000000 0.755453 -0.471161\nH 0.000000 -0.755453 -0.471161\n' > water.xyz

and write water-rhf.toml next to it:

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

[basis]
name = "sto-3g"

[method]
kind = "rhf"
OPENBLAS_NUM_THREADS=1 ferric water-rhf.toml

The output ends with:

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

Same molecule, same number. The CLI rejects any key it does not recognise, so a typo is an error rather than a silently ignored setting. Every key is listed in the input reference.

3. Add correlation

To go beyond Hartree–Fock, change the method and the basis. RI-MP2 needs an auxiliary (fitting) basis alongside the orbital basis, and STO-3G is too small for a meaningful correlation energy, so this example moves to cc-pVDZ:

bs  = ferric.BasisSet.bundled("cc-pvdz")
aux = ferric.BasisSet.bundled("cc-pvdz-ri")
mp2 = ferric.run_rimp2(water, bs, aux)
print(f"RI-MP2 total energy: {mp2.total_energy:.10f} Ha")
RI-MP2 total energy: -76.2308014541 Ha

In TOML the same calculation changes the [basis] name and the method kind, and adds an [mp2] section naming the auxiliary basis:

[basis]
name = "cc-pvdz"

[method]
kind = "rimp2"

[mp2]
auxbasis = "cc-pvdz-ri"

If auxbasis is omitted the CLI uses cc-pvdz-ri, but name it explicitly: the auxiliary basis should match the orbital basis. This is examples/water-rimp2.toml.

4. Where to next

You want toGo to
Pick a method for a chemistry questionChoosing a method
See what every method supports (open shell? gradients? CLI?)Capabilities and validation
Charged or open-shell molecules, geometry optimizationRecipes
The whole Python surfacePython bindings
Coming from PySCFFor PySCF users
Know what to trustCapabilities and validation

Running the bundled examples

The repository has one input file per method under examples/, indexed in Examples. They refer to molecules under testdata/, which the wheel does not include, so run them from a clone:

git clone https://github.com/mgoldey/ferric && cd ferric
OPENBLAS_NUM_THREADS=1 ferric examples/water-rimp2.toml
OPENBLAS_NUM_THREADS=1 ferric examples/water-attmp2.toml   # attenuated MP2, ω = 0.420 Å⁻¹

If you built from source rather than installing the wheel, replace ferric with cargo run --release --bin ferric --.

Sharp bits

Behaviour that differs from what you might expect. This page follows the pattern of JAX's "Sharp Bits": each entry says what surprises people, why ferric does it that way, and what to do.

Leave OPENBLAS_NUM_THREADS unset

What happens: the ferric CLI and import ferric pin OpenBLAS to one thread when OPENBLAS_NUM_THREADS is unset, and honour the variable when it is set. (.cargo/config.toml sets it to 1 for cargo-launched processes, also only when it is unset.) A value above 1 is therefore a deliberate choice, and its effect depends on the workload: it can speed up one calculation and slow another down several-fold.

Why: ferric's parallelism comes from rayon. OpenBLAS threads run inside rayon's worker threads, so the thread count is roughly BLAS threads × rayon threads. Measured on benzene with the ferric CLI, single runs on a heavily loaded machine with 6 physical cores (12 hardware threads), so read these as order-of-magnitude:

OPENBLAS_NUM_THREADSunset1612
RI-JK RHF (benzene-rhf-def2-rijk.toml)54 s31 s9.7 stimed out at 1800 s
PDEP-RPA (benzene-pdep-rpa.toml)5.9 s7.2 s22.9 s30.7 s

"unset" and 1 run the same configuration, so the gap between those two columns is the machine's load, not a setting. B3LYP timings were noisy, with no clear trend. None of these runs crashed.

Three effects matter beyond speed:

  • Oversubscription can stall a run. Once BLAS threads × rayon threads exceed the physical cores, a job can slow by orders of magnitude, as the 12-thread RI-JK run shows.
  • Results change slightly. The BLAS thread count changes the order of floating-point summation. The RI-JK energy at 6 threads differs from the 1-thread energy by 6e-6 Ha; expect differences at the 1e-6 to 1e-5 Ha level.
  • LU routines can crash. Threaded OpenBLAS LU factorization (dgetrf) can overflow the 2 MB stacks of rayon's worker threads. ferric calls LU solves in BSE, PCM, COSMO, MBD and orbital-rotation steps, among others.

Do: leave OPENBLAS_NUM_THREADS unset, and ferric runs with one BLAS thread. If you set it higher for a particular job, never above the number of physical cores, and check the timing and the energy against a one-thread run of the same input. For throughput across many molecules, run many single-threaded processes.

converged is a flag, and it does not mean "ground state"

What happens: run_rhf, run_uhf and run_rohf return a result object even when they hit max_iter; they do not raise. (run_dft, run_ksdft and the correlated drivers behave differently: they raise if their reference SCF does not converge.)

Why: a non-converged state is still useful for diagnosis, and raising would discard it.

Do: check result.converged in Python, or converged = true in CLI output, before using any number, and treat False as "no result". True means the SCF reached a stationary point, not necessarily the lowest one; open-shell molecules especially can land on an excited solution. Comparing UHF with ROHF, or a different starting guess, is a cheap check. See Python bindings.

energy_conv is a sanity bound, not a target

What happens: setting [scf] energy_conv very tight (say 1e-10) can make an SCF run tens of iterations past a settled density, or never converge.

Why: convergence is driven by the density criterion (density_conv). The energy change between iterations has a noise floor, and a tight energy bound can sit below it, so it is met only by chance. For a closed-shell run with exact J and K the floor comes from the incremental Fock update and its integral screening (integral_thresh). ferric estimates it as 0.0125 * integral_thresh * nbf^2.25 + 0.5 * eps * |E| * sqrt(nbf) Ha. The estimate against the measured RMS of the energy change once the density has settled:

systemnbfestimated floormeasured RMS
water / cc-pVDZ241.6e-114.5e-12
benzene / cc-pVDZ1145.3e-101.4e-10
alkane C48 / STO-3G3386.1e-92.2e-9
71-atom drug-like molecule / STO-3G2392.8e-91.9e-9 to 2.7e-9

The estimate is good to a factor of a few and errs high for large systems; it was fitted on systems up to about 340 basis functions, and beyond that it is an extrapolation. With density-fitted J (and in the open-shell solvers, which rebuild J and K every iteration) only rounding remains, about 0.5 * eps * |E| * sqrt(nbf): measured 3e-13 for benzene/cc-pVDZ with RI-JK and 1.3e-12 for alkane C20/6-31G with RI-JK. The CLI default is loose (1e-3) by design.

What ferric tells you: when you set energy_conv below 0.3 times the estimated floor, the CLI prints warning: [scf] energy_conv ... is below the estimated noise floor of dE ... to stderr and the Python run_rhf, run_uhf and run_rohf emit a RuntimeWarning, once, before the SCF starts. A default energy_conv is never warned about. Nothing else changes: the threshold is not clamped and the SCF stops exactly where it otherwise would. A run that ends without converging on the dE criterion alone repeats the statement in RhfResult.convergence_summary. No estimate is made for functionals (grid noise), the LinK and COSX exchange builders, PCM / COSMO, polarizable embedding or non-default screening: those runs get no warning, which is not a statement that the tolerance is reachable. The |E| in the rounding term is estimated from the nuclear charges (ECP cores removed, about 5 % for all-electron molecules, 3.7x high for an iodine ECP case); the point-charge or field energy of an embedding is not included.

A symptom of the same floor: with a large point-charge field (point_charges= on run_rhf's closed-shell direct-J/K path) the number of SCF iterations can differ between two orderings of the same charges, because a change of a few ulps in the one-electron potential decides which iteration the dE test happens to pass. On rc7 a 6458-charge field took 64 iterations nearest-first and 111 in file order at energy_conv=1e-10. The potential itself changes only by roundoff (3e-15 to 1e-14 relative) and the converged energy by about 1e-12 Ha. ferric turns the incremental update off when energy_conv < 1e4 * integral_thresh or density_conv < 1e3 * integral_thresh (1e-8 and 1e-9 at the default integral_thresh), where all measured orders took 20 iterations; at the default energy_conv and density_conv every measured order took the same number (15 iterations on the field and orders tried; the tests do not assert iteration counts, so this is a measurement, not a guarantee for other fields). run_uhf and run_rohf use a separate open-shell incremental path and run_dft's default RI-J does not use the direct incremental path: for those the order changes the potential by roundoff too, but iteration counts were not measured. ferric does not reorder the charges.

Do: tighten density_conv, not energy_conv.

Density fitting is on in run_dft but off in run_rhf

What happens: comparing a ferric DFT energy with a code that uses exact Coulomb gives a discrepancy that grows with molecule size.

Why: run_dft, run_ksdft and the CLI's ksdft density-fit Coulomb (RI-J) with def2-universal-jkfit by default, and, for functionals with exact exchange (hybrids and range-separated hybrids), exchange (RI-K) with the same basis. run_rhf builds exact four-centre J and K unless you pass df_j_aux/df_k_aux. The difference is the fitting error that density fitting always carries. Against exact J at PBE/STO-3G it is 0.28 kcal/mol for water, 1.16 for benzene and 9.5 for a 71-atom drug molecule.

Do:

  • To compare with an exact-Coulomb code (ORCA with NORI, PySCF without density_fit()), pass df_j_aux="exact" to run_dft or run_ksdft, and df_k_aux="exact" too for a hybrid. "", "none", "off" and "conventional" mean the same. In the CLI, set [scf] df_j_aux = "" and df_k_aux = "". The CLI's SCF J/K log line then reads RI-JK via with a blank name; the run uses exact J and K.
  • Or fit on both sides: density_fit(auxbasis="def2-universal-jkfit") in PySCF, or run_rhf(..., df_j_aux="def2-universal-jkfit", df_k_aux="def2-universal-jkfit") in ferric, so J and K are both fitted on both sides. run_rhf takes a basis name or "" here, not "exact".
  • run_qmmm (KS methods), run_gw and run_u_gw with xc, run_tddft with a functional, run_tdhf_static_polarizability, run_double_hybrid and run_rs_mp2_rpa always density-fit their reference SCF with def2-universal-jkfit and have no opt-out.

Units differ by interface and by accessor

QuantityWhereUnit
Molecule geometry input (from_xyz, from_xyz_string, .xyz files)Python, CLIÅngström
Coordinates inside the Rust libraryRustBohr
Attenuation ω: [mp2] omega (att-rimp2, rs-mp2-rpa), [mp2] mp2v_omega (mp2-v); omega= of run_attenuated_rimp2, run_rs_mp2_rpa, run_mp2_v, and terf_omega= of run_rs_mp2_rpaCLI TOML, PythonÅ⁻¹
Double-hybrid ω: [dft] omega (wb97x-l-v)CLI TOMLBohr⁻¹
tune_omega (bracket and result); omega= of compute_eri3_mo and compute_metric_2cPythonBohr⁻¹
Range-separation ωRust configsBohr⁻¹
QmmmSystem(...) coordinatesPythonÅngström
QmmmSystem.point_charges()PythonBohr
QmmmSystem.link_atom_positions()PythonÅngström
EnergieseverywhereHartree

Do: check the docstring of any coordinate accessor before converting it. Converting both QM/MM accessors "to be safe" makes one of them wrong by a factor of 1.89.

Energies take a BasisSet; geometry changes take a basis name

What happens: run_rhf(mol, ferric.BasisSet.bundled("sto-3g")) works, but run_optimize wants the string "sto-3g".

Why: entry points that move atoms (optimization, frequencies, saddle search) build the basis themselves for each geometry, so they take its name.

Do: check the signature in Python bindings.

Not everything is on the CLI

The CLI runs the method.kinds in the capability matrix, with task = energy, optimize or frequencies. It also has [qmmm], [cosmo], [pcm], [external_potential], [scf] stability_descent, [mp2] att_operator and [dft] grid_prune. These capabilities are Python-only:

CapabilityPythonWhat the CLI has instead
Transition-state search, IRCrun_saddle, run_ircno task for either
QM/MM MM forces, full-system gradient and optimization, smeared charges, Thole polarization, an MM force fieldrun_qmmm, run_optimize_qmmm, QmmmSystem, MmTopology[qmmm] embeds the QM region in fixed point charges from a PQR file, with link atoms and boundary schemes
Constrained DFT and electron-transfer couplingsrun_cdft, CdftConstraint, cdft_couplingnothing

Examples need the repository

examples/*.toml point at molecules under testdata/. Neither ships in the wheel. Clone the repository and run from its root; see Installation.

[memory] budget_gb does not cap the whole process

What happens: a job's resident memory can exceed budget_gb, and a job whose budget is too small usually still runs, slower, rather than failing. Benzene PBE/def2-SVP with budget_gb = 0.002 finishes with the same SCF iterations, a peak RSS of 157 MB, and 11.2 s of wall time instead of 3.5 s.

Why: the CLI turns budget_gb (or, when unset, 0.8 × available RAM) into one process-wide ledger. The large, size-dependent allocations reserve their bytes from it before allocating: three-index RI tensors, the DFT grid's AO cache, MO-transformed RI blocks, and the large tensors of the MP2, RPA, GW and CC methods. Two such allocations alive at the same time therefore cannot each claim the whole budget. Nothing else is charged: basis-sized matrices (Fock, density, DIIS history), integral engines, BLAS and per-thread scratch, allocator overhead. The spilled RI-JK path sizes its dressing and DF-K read-back widths to the budget. Below a floor of about 200 aux rows for the dressing (raw stream, one unpacked block and one GEMM result) and two DF-K chunks for the read-back, a budget overshoots, with a warning naming the phase. The read-back is sized for 8 workers; a run with more can overshoot by the extra per-worker scratch, and says so.

When a charged allocation does not fit, the result depends on the allocation:

  • The SCF's three-index tensor is stored whole when n_aux × n_bf² × 8 bytes fit. When only the symmetric half (n_aux × n_bf(n_bf+1)/2 × 8 bytes, plus 64 unpacked aux rows of scratch) fits, it is kept in memory as that packed triangle and the RI-J build reads the packed rows directly. When even that does not fit, it is either spilled to a file in $TMPDIR (/tmp by default) and reread on every iteration, or recomputed on every iteration without writing anything. auto chooses between those two from a rate measured on the machine; [scf] jk_storage forces memory, disk or direct. Holding the K dressing to a small budget takes several passes over the raw tensor, and a run making many says so when a pass costs IO or integrals. The dressing and K are designed to be bitwise the same spilled or in core, and tests confirm that at OPENBLAS_NUM_THREADS=1 on one OpenBLAS kernel (Haswell). Threaded BLAS moves bits, and other kernels are untested. J is a separate matter: which storage raw gets can follow the shared ledger, so J can differ by a few ulp between runs that charge the ledger differently.
  • A dressed tensor that fits the budget but not beside the resident raw tensor spills instead of stopping the job, unless jk_storage = "memory".
  • A DF-guess pre-stage that runs out of memory is abandoned with a warning, and the SCF starts from its usual guess. That changes iterations and can select a different SCF solution, a larger effect than any bit-level one. An explicit jk_storage = "memory" that does not fit, and any memory failure under MPI, stop the job instead. A spill directory that is full also stops it, and df_increments has no fallback.
  • The DFT grid AO cache is recomputed at every Fock build instead of stored. The energy is bit-identical. Under an explicit budget, XC takes its memory after the RI-J/K tensors, so it never pushes them out. An auto-detected budget first gives up 0.8 of the AO blocks XC expects to keep, so the raw tensor can drop to a packed or spilled tier while XC stays resident. If what is left cannot hold its per-build working set either, that set runs uncharged, a warning gives its size, and the run log records it. The job can exceed its budget by more than that figure, because other scratch is not charged either: danuglipron/def2-SVP warned of 0.46 GB and peaked 0.92 GB over a 15.3 GB budget. The working set scales with the thread count. The VV10 NLC cache has no fallback and is charged before RI-J/K, so a budget that cannot hold it alone stops the job before any integrals are built. It also leaves the MINAO guess's free-atom solves less room. If one runs out, that element's block of the guess is left zero, so the SCF starts from a partial MINAO guess rather than from hcore. If that was every element in the molecule, the density is zero and the first Fock matrix is hcore. A warning names the element and says which, the run log records it, and the SCF may take more iterations. Very near that limit, repeat runs of one input have differed in whether this happens. jk_storage = "memory" with a VV10 functional is held to the budget less the cache, so it refuses budgets that cannot hold the cache and the tensors together.
  • An allocation with no fallback stops the job with an error naming it. For some methods this check comes after the SCF. Benzene RI-MP2/def2-SVP with budget_gb = 0.001 completes the SCF, then exits with RI-MP2 MO-side blocks ... requires 0.01 GB; budget is 0.00 GB.

Some stages print a warning when resident memory passes 110% of the budget. The warning never stops the run.

From Python, memory_budget_gb= sets the same per-allocation limits, but no shared ledger is installed. Each check compares its own allocation with the whole budget, not with what the other allocations have left. Two checks instead subtract the process's current RSS and allow 90% of the remainder: the KS-DFT grid AO cache (store or recompute) and the UKS Newton/TRAH fxc kernel's second grid cache. The fxc check subtracts RSS in the CLI too.

Do: leave room below the machine's real limit for the uncharged part. On a shared machine, also cap the process externally so a runaway job dies in its own cgroup, for example with scripts/ferric-limited -- ferric input.toml (defaults MemoryMax=12G, MemoryHigh=10G, no swap) or systemd-run --user --scope -p MemoryMax=12G -- ferric input.toml.

GPU offload is opt-in and may not help

The CUDA backend is not in the PyPI wheel; it needs a source build with the gpu feature (see GPU (CUDA)), and even then it is off by default (FERRIC_GPU=auto or [gpu] mode turns it on). Consumer GeForce cards run FP64 at 1/32 of their FP32 rate or less, so offloading f64 contractions may be slower than the CPU once transfer time is counted; whether it helps depends on the card and the problem, and has to be measured on your own hardware.

The Kohn-Sham XC grid runs on the device only under mode = "on" (or gpu = "on" / "mixed"), never under auto: on a GTX 1080 every device XC build took 2.2-3x the CPU time and whole PBE SCFs ran about 2x slower, so auto keeps it on the CPU rather than slowing every DFT run on a GPU box.

Device memory is a separate pool ([gpu] memory_gb); the host [memory] budget_gb is not reduced by it. A contraction that does not fit the device pool runs on the CPU and is counted, not refused. FERRIC_GPU_TRACE=1 prints each fallback.

[precision] mode = "mixed" (or its deprecated alias [gpu] precision = "mixed") changes the numbers as well as the speed: the audit line, ferric.gpu_status()["precision"] and each result's precision provenance record (run_end.extra.precision in the run log, result.precision in Python) say which precision a run used, and the per-kernel error map is on the validation page. The one shipped mixed kernel is rimp2-energy (f32-resident B_ov; closed-shell and unrestricted RI-MP2 under the Coulomb, erfc and terfc operators); its measured energy errors are 3e-11 to 7e-9 Eh closed-shell Coulomb (total, with the OS and SS parts up to 5e-9 Eh each), 5e-10 to 1.4e-8 Eh unrestricted Coulomb (total, with the αα, ββ and αβ blocks up to 1.1e-8 Eh each) and 7e-11 to 6e-9 Eh for erfc and terfc on the systems listed there, graded as a measured error map, not Proven; a mixed run prints precision mixed in its [ferric] gpu: line, and its energies are not interchangeable with f64 results in a convergence or reference comparison. A tiny problem still pays the device upload when the mode is on, whatever the precision. gpu = "mixed" (or [gpu] preset = "mixed") is mode = "on" plus precision = "mixed", so it carries the same change in the numbers; a mode or precision setting that contradicts the preset is an error, not an override. A mixed_kernels list given next to a preset is kept as given instead of the preset's set (it may narrow it); a name this build does not ship is refused, and the opt-in xc-vxc and dfk-occ are admitted only when the list names them and mode resolves to "on" (so gpu = "mixed" with the list works, auto-mixed with it is an error).

Implemented is not validated

A method listed on these pages exists and runs. How closely its numbers have been checked against an independent reference differs a lot from method to method. Look up the grade in Capabilities and validation before relying on a number.

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

Choosing a method

Start from what you want to compute. Each row names a method that ferric implements for that task, where to run it (CLI method.kind or Python only), how far it is validated, and a shipped example to copy. Grades, and the anchors behind them, are on Capabilities and validation. Cost is given as formal scaling with system size N, not as timings.

This page only recommends what the code and its tests support. Where ferric has no validated option for a task, the row says so.

By task

TaskRecommendedWhereGradeScalingExample
Geometry optimizationKS-DFT (e.g. PBE, B3LYP) or RHF, task = "optimize"CLI, Python (run_optimize is RHF)ProvenN⁴h2-lda-opt.toml, h2_opt.toml
Harmonic frequenciesKS-DFT or RHF/UHF/ROHF, task = "frequencies" (analytic Hessian for RHF/UHF/ROHF with exact J/K, and for RHF, UHF and ROHF with RI-J and/or RI-K; the analytic moving-grid Hessian for closed-shell KS LDA/GGA, pure or global hybrid; both under the default hessian = "auto"; otherwise finite differences of the analytic gradient, 6N gradients)CLI, Python run_frequenciesenergies Proven; check the printed Hessian asymmetry6N × N⁴water-frequencies.toml
Transition staterun_saddle (P-RFO), then run_irc to confirm which minima it connectsPython only, closed shellsee Capabilities and validation~2(6N+1) gradients + steps—
Conformer or reaction energies, routineKS-DFT + D3(BJ)CLI ksdft + [dft] dispersion = "d3bj", Python run_dft(dispersion=...)DFT Proven; D3(BJ) (with or without ATM) matches simple-dftd3 to <1e-12 HaN⁴ (DFT)water-pbe-d3bj.toml
Correlated energies, small to mediumRI-MP2CLI rimp2, Python run_rimp2ProvenN⁵water-rimp2.toml
Correlated energies, benchmark quality, smallCCSD(T) (closed shell)Python run_ccsd_t only; CCSD also CLI ccsdCCSD Proven; (T) matches PySCF ~1e-6 Ha on H2O/cc-pVDZN⁶ / N⁷water-ccsd.toml (H2)
Non-covalent interaction energiesAttenuated MP2 in the basis its parameters were fitted for: SCS-MP2(2terfc) or MP2-V in aug-cc-pVTZ, erfc att-MP2 in aug-cc-pVDZ (the basis of the 2012 paper); or KS-DFT + D3(BJ)CLI scs-mp2-2terfc, mp2-v, att-rimp2, ksdftatt-MP2, 2terfc and MP2-V Proven (narrow) as implementations; the MP2-V parameterization is unbenchmarkedN⁵water-scs-mp2-2terfc.toml, water-mp2v.toml, water-attmp2.toml
Long-range correlation from responseRS-MP2 + LR-RPA, or PDEP-RPACLI rs-mp2-rpa, pdep-rpaRS-MP2+RPA Smoke; PDEP-RPA ProvenN⁵ (MP2 part); N⁴ (RI-RPA)water-rs-mp2-rpa.toml, water-pdep-rpa.toml
Ionization potentials / electron affinitiesG0W0@PBE (or @HF); U-GW for open shellsCLI gw, Python run_gw, run_u_gwSmoke, about ±0.3 eV—water-g0w0-pbe.toml, oh-ugw.toml
Excitation energiesBSE-TDA on G0W0@HF; or TDA/TDDFT (CIS/TDHF on an HF reference)CLI bse-tda, tda, tddft; Python run_bse_tda, run_tddftBSE-TDA Smoke; TDA/TDDFT Proven (narrow, closed shell)—water-bse-tda.toml, water-tda.toml
Static polarizabilityPDEP-RPA properties, or RPAx@KS (static α only)CLI pdep-rpa with compute_polarizability, tdhf-static-polarizabilityPDEP-RPA Proven (energy); RPAx SmokeN⁴ (RI)h2o-pdep-rpa-props.toml, water-tdhf-static-alpha.toml
\( C_6 \) dispersion coefficientsPDEP-RPA dynamic α (c6_source = "pdep") or TS/MBD, in an augmented basis. Not RPAx: its \( C_6 \) is ~63% lowCLI pdep-rpa + [rpa] compute_c6which source is better is not establishedN⁴ (RI)water-c6-pdep.toml, argon-c6-rpa-pbe.toml
Implicit solvationIEF-PCM (CLI [pcm], Python solvent=) or COSMO (CLI [cosmo])see SCF and DFTboth cross-checked against PySCF on waterSCF cost—
Embedding in a protein or solventQM/MMPython run_qmmm; CLI [qmmm] (point charges)see QM/MMSCF costwater-qmmm.toml
Electron-transfer couplingconstrained DFT + Wu–Van Voorhis \( H_{ab} \)Python run_cdft, cdft_coupling (no CLI)no external referenceSCF cost × outer λ loop—
Atomic charges, ESPLöwdin, Hirshfeld, CHELPG, RESP; ESP at nuclei or on the surfacePythonsee Capabilities and validationSCF cost—

Notes on the table:

  • "Proven" for an attenuated MP2 method means the implementation reproduces its reference, not that the method is accurate on your system. Its parameters are fitted to interaction-energy benchmarks in one basis (the aug-cc-pVTZ sets without counterpoise, with frozen core); in another basis, with counterpoise correction, or with frozen_core = 0 (the default), you are extrapolating. See The MP2 family.
  • KS-DFT uses density fitting by default (def2-universal-jkfit), and the fitting error grows with system size. Turn it off or match it before comparing with an exact-Coulomb code; see SCF and DFT.
  • CC results are sensitive to the aux basis: at CC accuracy the RI error depends on the auxiliary set. See Coupled cluster.

Pairing an orbital basis with its auxiliary basis

Every correlated method needs an RI auxiliary basis ([mp2] auxbasis or [rpa] auxbasis; the auxbasis argument in Python). Use the set built for your orbital basis. The bundled pairs are:

Orbital basisRI (correlation) auxJK aux (for df_j_aux / df_k_aux)
cc-pVDZ / TZ / QZcc-pvdz-rifit (alias cc-pvdz-ri), cc-pvtz-rifit, cc-pvqz-rifitdef2-universal-jkfit; cc-pvtz-jkfit / cc-pvqz-jkfit (H, B-F, Al-Cl, Ga-Br only)
aug-cc-pVDZ / TZ / QZaug-cc-pvdz-rifit, aug-cc-pvtz-rifit, aug-cc-pvqz-rifitaug-cc-pvdz-jkfit, aug-cc-pvtz-jkfit, aug-cc-pvqz-jkfit (H, B-Ne, Al-Ar, Ga-Kr; Ne/Ar/Kr extrapolated), else def2-universal-jkfit
cc-pVXZ-PP, aug-cc-pVXZ-PP<orbital>-pp-rifit (H-Ar, without Li and Na in the aug sets; Sc-Ni at TZ and QZ only; Y-Pd; Hf-Pt. K-Ni at DZ, K and Ca at TZ/QZ, Cu-Sr, Ag-Lu and Au-Rn error)def2-universal-jkfit
def2-SVP(D), TZVP(P)(D), QZVP(P)(D)def2-<x>-rifit for eachdef2-universal-jkfit

Each RIFIT set in the table also loads under ORCA's <orbital>/c name (for example def2-tzvp/c), each JKFIT set under <orbital>/jk (for example aug-cc-pvtz/jk), and def2-universal-jkfit as def2/jk. STO-3G, the Pople sets and the pcseg family have no matching RI set; pair them with a def2 or cc RIFIT set of similar quality. Any other orbital set goes in through [basis] path as a Gaussian-94 file (orbital basis only). def2-universal-jkfit is the default JK set for KS-DFT.

When nothing here fits

  • Analytic Hessians cover RHF, UHF and ROHF (exact J/K, or RI-J / RI-K with no external potential; an ECP is fine; basis up to f functions; RHF and UHF with exact J/K also allow an external potential, ROHF allows none on any route), and closed-shell KS LDA/GGA (pure or global hybrid, moving-grid Hessian). Other KS-DFT (UKS, ROKS, meta-GGA, range-separated hybrids) and correlated methods' frequencies are by finite differences of the analytic gradient.
  • Open-shell coupled cluster and open-shell TS search are not available (the one open-shell CC-type method, LinLCCD(hh), is library-only). Open-shell KS-DFT is available from the CLI: ksdft with multiplicity > 1 runs UKS, and uhf/rohf with [dft] functional run UKS/ROKS. See Capabilities and validation: open shells in the CLI.
  • TDDFT with the XC kernel exists only as a library-only spike; the CLI and Python TDDFT omit it. See RPA, GW and excited states.

For PySCF users

ferric's Python API will look familiar if you use PySCF, but it is built differently. This page maps the PySCF calls you already know onto ferric's, then lists the differences that change numbers or cause errors.

The table only lists pairs where both sides exist. If a PySCF feature has no row, assume ferric does not have it; check the full function reference to be sure. The PySCF names were checked against PySCF 2.12.1.

Rosetta table

Assume import ferric and, on the PySCF side, the matching from pyscf import ... (gto, scf, dft, mp, cc, tdscf, gw, lo, df, qmmm, solvent, geomopt, hessian).

Molecule and basis

PySCFferric
mol = gto.M(atom=..., basis="cc-pvdz", charge=0, spin=2)mol = ferric.Molecule.from_xyz_string(xyz, charge=0, multiplicity=3) and bs = ferric.BasisSet.bundled("cc-pvdz")
mol.energy_nuc()mol.nuclear_repulsion()
mol.nelectronmol.nelec()
mol.natmmol.natoms()
mol.atom_coords() (Bohr)mol.coords_bohr()
mol.atom_coords(unit="Angstrom")mol.coords()
mol.elementsmol.symbols()
mol.atom_charges()mol.atomic_numbers() (true Z, not reduced by an ECP)
ghost atom "ghost-H" or "X-H"@H in the XYZ text

SCF and DFT

PySCFferric
mf = scf.RHF(mol).run()r = ferric.run_rhf(mol, bs)
scf.UHF(mol).run()ferric.run_uhf(mol, bs) (spin from mol)
scf.ROHF(mol).run()ferric.run_rohf(mol, bs)
scf.RHF(mol).density_fit(auxbasis="def2-universal-jkfit")ferric.run_rhf(mol, bs, df_j_aux="def2-universal-jkfit", df_k_aux="def2-universal-jkfit")
mf = dft.RKS(mol); mf.xc = "b3lyp"; mf.run()ferric.run_dft(mol, bs, functional="b3lyp") (closed-shell only)
mf.disp = "d3bj"ferric.run_dft(..., dispersion="d3bj"), or ferric.d3bj_energy(mol, "b3lyp") alone
mf.e_totr.energy (RHF/UHF/ROHF) or r.total_energy (DFT)
mf.convergedr.converged
mf.mo_energyr.orbital_energies() (RHF), r.orbital_energies_alpha() / _beta() (UHF/ROHF)
mf.mo_coeffr.mo_coefficients() (RHF only)
mf.make_rdm1()r.density() (RHF, DFT), r.density_alpha() / r.density_beta() (UHF/ROHF)
mf.level_shift = 0.2run_rhf(..., level_shift=0.2)
mf = solvent.PCM(scf.RHF(mol)); mf.with_solvent.eps = 78.4ferric.run_rhf(mol, bs, solvent=78.4) or solvent="water"
qmmm.mm_charge(mf, coords, charges)run_rhf(..., point_charges=[(q, x, y, z), ...]), coordinates in Bohr

Geometry and vibrations

PySCFferric
geomopt.geometric_solver.optimize(mf)ferric.run_optimize(mol, "cc-pvdz") (RHF; basis by name)
mf.Hessian().kernel() then thermo.harmonic_analysis(mol, h)ferric.run_frequencies(mol, "cc-pvdz")

Correlation, response and properties

PySCFferric
mp.MP2(mf).density_fit(auxbasis="cc-pvdz-ri").run()ferric.run_rimp2(mol, bs, ferric.BasisSet.bundled("cc-pvdz-ri"))
cc.CCSD(mf).density_fit(auxbasis=...).run()ferric.run_ccsd(mol, bs, aux)
mycc.ccsd_t()ferric.run_ccsd_t(mol, bs, aux).t_correction
tdscf.TDA(mf).run()ferric.run_tddft(mol, bs, aux, method="tda")
tdscf.TDHF(mf).run() / tdscf.TDDFT(mf).run()ferric.run_tddft(mol, bs, aux, method="casida")
gw.GW(mf).kernel()ferric.run_gw(mol, bs, aux)
lo.Boys(mol, mo).kernel()ferric.boys_localize(mol, bs, mo).c_loc()
mf.mulliken_pop()ferric.mulliken_charges(mol, bs, r)
df.incore.aux_e2(mol, auxbasis)ferric.compute_eri3(mol, bs, aux)
df.incore.fill_2c2e(mol, auxbasis)ferric.compute_metric_2c(mol, bs, aux)

Integrals and building blocks

With ints = ferric.AoIntegrals(mol, bs) (see Building blocks). AO matrices follow libint2's basis-function conventions, not PySCF's, so raw AO matrices differ between the two codes; energies and other invariant quantities agree.

PySCFferric
mol.intor("int1e_ovlp"), "int1e_kin", "int1e_nuc"ints.overlap(), ints.kinetic(), ints.nuclear()
mf.get_hcore()ints.hcore()
mol.intor("int1e_r")ints.dipole()
mol.intor("int2e")ints.eri()
mf.get_jk(mol, dm)ints.jk(dm)
scf.RHF(mol).density_fit(auxbasis=...).get_jk(mol, dm)ints.df_jk(aux).jk(dm)
ao2mo.general(mol, (c1, c2, c3, c4), compact=False)ints.ao2mo(c1, c2, c3, c4) (4-index, not pair-packed)
lib.einsum(spec, a, b)ferric.einsum(spec, a, b) (two operands)

How ferric differs

Stateless calls, not mutable method objects. PySCF builds an mf object, lets you set attributes on it, then runs it. ferric has one function per calculation. Settings are keyword arguments, and the call returns a result object. There is nothing to reconfigure and rerun; call the function again. Correlated drivers run their own reference SCF, so you pass them the molecule and basis, not a converged mf.

Spin is multiplicity, and it lives on the molecule. PySCF's spin is 2S; ferric's multiplicity is 2S+1. It is set when the molecule is built (from_xyz_string(..., multiplicity=3)). run_uhf and run_rohf have no spin keyword.

The auxiliary basis is always explicit. PySCF picks an auxiliary basis for you when you call .density_fit() without one. ferric's MP2, CC, RPA, GW and TDDFT drivers take the RI basis as a required argument. Only SCF-level fitting has a default: run_dft and run_ksdft fit J (and K for hybrids) with def2-universal-jkfit unless you pass df_j_aux/df_k_aux. These fit their reference SCF with def2-universal-jkfit and take no override: run_qmmm with a KS method, run_gw and run_u_gw with xc, run_tddft with a functional, run_tdhf_static_polarizability, run_double_hybrid, and run_rs_mp2_rpa (its HF reference).

Density fitting is on in run_dft and off in run_rhf. run_dft uses RI-J by default, and RI-K for hybrids. dft.RKS in PySCF uses exact Coulomb unless you call .density_fit(). run_rhf builds exact four-centre J and K unless you pass df_j_aux/df_k_aux. The difference between a fitted and an exact energy is the fitting error, not a bug; the ferric source records it at PBE/STO-3G against conventional J as 0.28 (water), 1.16 (benzene) and 9.5 kcal/mol (a 71-atom drug molecule). Pass df_j_aux="exact" to run_dft when you compare against exact-Coulomb PySCF.

The DFT grid is smaller and not pruned. ferric's default is 75 radial × 110 Lebedev angular points on every atom, with no pruning. PySCF's default (grids.level = 3) uses 302 angular points for H through Ne and 434 from Na on, with 50 radial points for H and 75 for C–Ne, and prunes with nwchem_prune. Expect grid-level differences in DFT energies.

Correlated methods use RI integrals throughout. ferric's CCSD and CCSD(T) build their integrals from the auxiliary basis you pass. The fair PySCF comparison is cc.CCSD(mf).density_fit(...), not plain cc.CCSD(mf), which uses exact four-index integrals.

Range-separation ω is in Å⁻¹ in the run_* drivers. omega on run_attenuated_rimp2 and run_rs_mp2_rpa is in Å⁻¹ (default 0.420). PySCF, and ferric's low-level compute_eri3_mo and compute_metric_2c, take Bohr⁻¹.

Point charges are in Bohr; QmmmSystem coordinates are in Å. point_charges= on run_rhf and the other SCF drivers takes Bohr. PySCF's qmmm.mm_charge defaults to mol.unit, which is Ångström unless you changed it. ferric.QmmmSystem takes Ångström coordinates, and its point_charges() accessor returns Bohr, ready to pass to run_rhf.

Only some drivers raise on an unconverged SCF. PySCF's kernel() returns the energy either way. ferric's run_rhf, run_uhf, run_rohf and run_qmmm do the same, so check .converged. run_dft and every correlated driver raise instead when their SCF does not converge.

TDDFT refuses some functionals. ferric's run_tddft includes the f_xc response and matches PySCF TDA/TDDFT to 1e-3 eV for HF, LDA, PBE and B3LYP on the tested systems. It refuses meta-GGA, VV10 and range-separated functionals, which PySCF accepts, and it runs closed-shell references only.

GW defaults to a Hartree–Fock reference. PySCF's gw.GW(mf) runs on whatever mf you pass, usually a DFT one. ferric.run_gw runs its own HF reference unless you pass xc="pbe" or another functional.

The PCM defaults differ. ferric's solvent= is IEF-PCM with 110 tesserae per atomic sphere (pcm_lebedev_order=110). PySCF's PCM defaults to C-PCM with Lebedev order 29 (302 points per sphere) and SWIG discretization. The named solvent "water" is ε = 78.4 in ferric; PySCF's default ε is 78.3553.

AO matrices do not match element by element. ferric's AO basis-function conventions come from libint2 and are not the same as PySCF's (libcint). Measured on CO/cc-pVDZ: RHF total energies agree to 3e-12 Ha and orbital energies to 8e-9 Ha, while the two AO density matrices differ element by element by up to 1.5. Compare invariant quantities (energies, orbital energies, charges), not raw AO matrices. Axis order differs too: compute_eri3 returns (naux, n_bf, n_bf), with the auxiliary index first.

Basis sets come from a fixed bundled list. BasisSet.bundled(name) knows 64 sets plus aliases (see Bundled basis sets). BasisSet.from_bse_json(path) loads a Basis Set Exchange JSON file; there is no per-element basis dictionary.

The Pople sets use Gaussian's d convention, not PySCF's default. PySCF builds every molecule spherical (5D) unless you pass cart=True, and the flag covers the whole molecule. ferric types each shell:

BasisferricMatching PySCF call
6-31G, 6-31G*, 6-31G**, 6-31+G*, 6-31+G**, 6-31++G**Cartesian d (6D)gto.M(..., cart=True)
6-311G**, 6-311++G**spherical d (5D) on every elementthe default (cart=False)
every other bundled setspherical, as the Basis Set Exchange types itthe default

The 6D/5D difference is not small: water/6-31G* differs by about 1.4 mHa between the two. For the 6-311G family ferric departs from the Basis Set Exchange, which marks the Na-Ar d shells Cartesian; ferric makes them spherical like every other element, so a molecule such as CH3Cl is 5D throughout and matches PySCF's default. The f shells the 6-31G* family carries on Sc-Zn are spherical (7F), as in Gaussian; cart=True would make them Cartesian too, so PySCF's single flag cannot reproduce 6-31G* on Sc-Zn.

Results come back as numpy arrays or lists, not live objects. Matrices are numpy.ndarray. Per-atom quantities such as charges are Python lists.

QM/MM

Run a quantum region inside a classical environment: a ligand in a protein pocket, a reacting site in an enzyme, a solute in explicit solvent. The QM atoms get a real wavefunction; everything else becomes point charges that polarize it.

What you get: QM region selection (by index, by radius, or by whole residue), link atoms across covalent cuts with four boundary-charge schemes, Gaussian-smeared charges, Thole polarizable embedding, an AMBER-form MM crate, analytic QM and MM forces, a full-structure gradient across the cut, geometry optimization.

What is missing: no AMBER prmtop reader (go through OpenMM), and no periodic boundary conditions.


Before you start: relax the geometry with xtb

DFT on an unrelaxed structure wastes hours and often fails.

Measured on a 27-atom carbocation built by hand:

hand-built    C7-H24 = 0.902 Å      SCF: Stalled@37, never converged
xtb GFN2      C7-H24 = 1.084 Å      SCF: converged
                                    545 kcal/mol lower

An SCF has no reason to converge on a structure with a 0.9 Å C–H bond. xtb fixes it in seconds; DFT spends hours failing on it.

xtb input.xyz --opt --gfn 2 --chrg 1        # seconds
ferric single_point.toml                     # then the expensive step

Use it for the initial geometry of anything you did not get from a crystal structure or a previous optimization: hand-built ligands, docked poses, edited residues, anything from a SMILES string.

Build note: xtb built with -O3 miscompiles gradients on this platform. Build with -Doptimization=2.


The smallest working example

import ferric

# The full structure: every atom, QM and MM alike.
symbols = ["O", "H", "H",   "O", "H", "H"]     # two waters
coords  = [[0.0, 0.0, 0.0], [0.76, 0.59, 0.0], [-0.76, 0.59, 0.0],
           [0.0, 0.0, 3.0], [0.76, 0.59, 3.0], [-0.76, 0.59, 3.0]]
charges = [-0.834, 0.417, 0.417,  -0.834, 0.417, 0.417]   # MM partial charges (e)

# QM = the first water; the second becomes point charges.
sys = ferric.QmmmSystem(symbols, coords, charges, qm_indices=[0, 1, 2])

res = ferric.run_qmmm(sys, "cc-pvdz", method="rhf")
print(res.energy)

charges are ignored for atoms that end up in the QM region, so you can pass a full force-field charge set and let the selection decide.

Selecting the QM region

Three ways:

# 1. Explicit indices — full control.
ferric.QmmmSystem(symbols, coords, charges, qm_indices=[0, 1, 2])

# 2. Everything within a radius of some seed atoms.
ferric.QmmmSystem(symbols, coords, charges,
                  qm_seeds=[0], qm_radius_angstrom=5.0)

# 3. Whole residues — a residue joins if any of its atoms is in range.
#    Prefer this for proteins: a radius cut alone will slice through a
#    residue and leave you with a chemically meaningless fragment.
ferric.QmmmSystem(symbols, coords, charges,
                  qm_seeds=[0], qm_radius_angstrom=5.0,
                  residue_ids=residue_ids)

residue_ids together with qm_indices is an error: an explicit index list and a residue expansion are contradictory instructions.

Cutting a covalent bond

If the QM/MM boundary crosses a bond, you need a link atom and a scheme for what to do with the host's charge:

sys = (ferric.QmmmSystem(symbols, coords, charges, qm_indices=qm)
       .with_link_atoms(bonds)                        # bonds crossing the cut
       .with_boundary_charges(bonds, "rcd"))          # keep | delete-host | rc | rcd
schemewhat it does
keepleave the host charge in place
delete-hostZ1 — delete it
rcLin–Truhlar redistributed charge
rcdredistributed charge and dipole

The parser is strict: an unknown string is an error.

Energies, forces, and optimization

res = ferric.run_qmmm(sys, "cc-pvdz", method="uhf")
# KS-DFT: method="rks" or "uks" and xc, e.g. method="rks", xc="PBE".
# xc without rks/uks (or rks/uks without xc) raises ValueError.
res.energy
res.qm_gradient()      # dE/dR on the QM atoms
res.mm_forces()        # forces on the MM sites
res.full_gradient()    # across the cut, link rows folded back onto the frontier

opt = ferric.run_optimize_qmmm(sys, "cc-pvdz", method="rhf", move_mm="none")

move_mm chooses which MM atoms relax: "none" (the default, where only QM atoms move), ("within", radius_angstrom), ("residues", [ids]) (the system must have been built with residue_ids=), or "all". Any value other than "none" requires mm_topology=: without a force field, nothing holds the moving MM atoms' geometry together.

Polarizable embedding

Thole polarizable sites, if fixed charges are not enough:

sys = ferric.QmmmSystem(symbols, coords, charges,
                        qm_indices=qm,
                        polarizabilities_angstrom3=alphas)
res = ferric.run_qmmm(sys, "cc-pvdz", method="rhf", thole_a=2.1304)
res.e_pol                  # the polarization energy
res.induced_dipoles()

What is validated, and how far

Checked against pyscf.qmmm.mm_charge: the energy shift agrees to <1e-8 Ha, MM forces to 3e-10. An empty or all-zero MM region is bit-identical to a gas phase calculation.

The QM–MM Lennard-Jones term across a covalent cut is checked against OpenMM's own exclusion and 1-4 handling (ethanol cut at C–C, realistic LJ): energy and gradient agree to ~1e-18 Ha. With no bond path between the regions it is bit-identical to the plain every-pair sum.

Not validated: periodic boundary conditions (absent).

From the CLI

A [qmmm] section runs QM/MM from a TOML file, with no Python needed. The QM region becomes the molecule that is solved, and the MM region becomes the external potential it is solved in. A complete, runnable input is examples/water-qmmm.toml (repository, not the wheel):

[molecule]
xyz = "testdata/molecules/water.xyz"   # required by the parser, not read with [qmmm]
charge = 0                             # apply to the QM region
multiplicity = 1

[basis]
name = "sto-3g"

[method]
kind = "rhf"
task = "energy"

[qmmm]
pqr = "testdata/molecules/water_na.pqr"
qm_indices = [0, 1, 2]            # zero-based; or: qm_seeds = [0], qm_radius_angstrom = 1.5
# link_bonds = [[0, 3]]           # required when the cut crosses a covalent bond
# boundary_scheme = "delete-host" # default; also "keep", "rc", "rcd"

Measured on that file (water QM, one Na⁺ 4 Å away as MM, STO-3G): vacuum −74.9629466809, embedded −74.9653197421 Ha (−1.489 kcal/mol). That matches ferric.run_rhf(point_charges=...) to all ten printed digits.

The geometry and the MM charges both come from the PQR. An xyz has no partial charges, and an MM region without charges contributes nothing. [molecule] xyz must still be present, because the parser requires it, but it is not read when [qmmm] is present. That matters when you compute a vacuum reference: delete [qmmm] and ferric falls back to the xyz. If the xyz holds a different geometry, you are comparing two different molecules. In the example above, the xyz is an optimized water that gives −74.9631468000, which is 0.13 kcal/mol of error in the difference. Compare at one geometry.

boundary_scheme defaults to "delete-host", not "keep". Keeping the host charge across a covalent cut puts a bare point charge inside the link atom's bond length (measured 0.443 Å on an ethane C–C cut). A geometry optimization in that field diverges instead of failing with an error. "keep" is still available and is the right choice when the cut isn't covalent. An unknown scheme name is an error.

[qmmm] and [external_potential] together are refused. The MM region is an external potential, so combining them would count a contribution twice.

Scope of the CLI section: it reads only a PQR, because it needs charges and geometry together, and does electrostatic embedding only. Polarizable sites, smeared charges, the MM force field and MM relaxation are Python-only.

Known limits

  • No AMBER prmtop reader. Go through OpenMM, or build the arrays.
  • No periodic boundary conditions.
  • The pocket field is fixed unless you ask otherwise. move_mm="none" is the default in run_optimize_qmmm; the MM sites do not relax with the QM region until you widen it.
  • QM/MM dispersion is the force field's Lennard-Jones only. D3/D4/XDM/VV10 are QM-atom-pairwise and never see the MM atoms. With mm_topology= set, the MM force field supplies Lennard-Jones between every QM real atom and every MM atom (link atoms and boundary charges carry none). Without it there is no QM–MM van der Waals term at all.
  • QM–MM Lennard-Jones follows the force field's exclusion rules across the cut. Pairs 1-2 or 1-3 apart through the topology's bond list (the cut bond included) get no LJ, and 1-4 pairs are scaled by the topology's scale_lj_14 (AMBER default 0.5). The MM–MM terms use the same bond graph, so the cut bond has to be in the topology's bonds. QM–MM electrostatics do not use these exclusions or scale_coul_14. The embedding puts every MM charge in the QM Hamiltonian, and the boundary scheme (keep/delete-host/rc/rcd) handles the charges next to the cut.
  • The MM crate is AMBER-form (harmonic bonds and angles, periodic torsions, Lennard-Jones, Coulomb), validated against OpenMM.

Export: cube files and NPZ bundles

ferric writes volumetric data as Gaussian cube files and per-molecule property bundles as compressed NumPy .npz archives. The Python functions evaluate basis functions, the density and orbitals at arbitrary points; the CLI [rpa] section writes the NPZ bundle and PDEP eigenpotential cubes. The array schema is in crates/ferric-export/theory.md.

Units

QuantityUnit
Points, origins, spacings, padding, cube gridsBohr
Densitye/Bohr³
Orbitals and AO valuesBohr^(-3/2)
NPZ coordsBohr
NPZ energies, ESP, fieldHartree, Hartree/e, a.u.
NPZ polarizabilitiesBohr³
NPZ chargese

These differ from Molecule.from_xyz_string, which reads Å, and from the periodic and QM/MM Python entry points, which take Å. The functions below take Bohr throughout.

Cube files from Python

write_cube writes the total density or one restricted MO on an axis-aligned grid. Without origin and shape the grid is the atoms' bounding box plus padding. It returns the (origin, shape) used. what="mo" needs mo_index (0-based, energy order) and a restricted RHF or RKS result. Orbital signs are the SCF's arbitrary phase.

import ferric

water = ferric.Molecule.from_xyz_string("""3
water
O   0.000000   0.000000   0.117790
H   0.000000   0.755453  -0.471161
H   0.000000  -0.755453  -0.471161
""")
bs = ferric.BasisSet.bundled("sto-3g")
rhf = ferric.run_rhf(water, bs)

origin, shape = ferric.write_cube("density.cube", water, bs, rhf, spacing=0.3)
print(shape)
origin, shape = ferric.write_cube("homo.cube", water, bs, rhf,
                                  what="mo", mo_index=4, spacing=0.3)
print(shape)
[28, 38, 32]
[28, 38, 32]

Values at points

eval_ao_on_points, eval_density_on_points and eval_mo_on_points take an (N, 3) array of points in Bohr.

FunctionReturns
eval_ao_on_points(mol, bs, points)(N, nbasis) AO values, PySCF eval_gto layout
eval_density_on_points(mol, bs, result, points)list of N total densities (α + β)
eval_mo_on_points(mol, bs, result, mo_indices, points)(N, len(mo_indices)) restricted MO values

result is an RhfResult or DftResult. eval_mo_on_points refuses open-shell results. All three check the allocation against the memory budget before evaluating.

import numpy as np

pts = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]])    # Bohr
print(np.round(ferric.eval_density_on_points(water, bs, rhf, pts), 4))
print(ferric.eval_ao_on_points(water, bs, pts).shape)
print(ferric.eval_mo_on_points(water, bs, rhf, [4], pts).shape)
[10.2392  0.8103]
(2, 7)
(2, 1)

NPZ bundles from the CLI

A pdep-rpa run with [rpa] export_npz = "<path>" writes the bundle. The compute_* keys then default to true; the full key list and defaults are in Input file (TOML).

[molecule]
xyz = "testdata/molecules/water.xyz"

[basis]
name = "cc-pvdz"

[method]
kind = "pdep-rpa"
task = "energy"

[rpa]
auxbasis = "cc-pvdz-ri"
n_quad = 20
trunc_thresh = 1e-4
export_npz = "h2o_pdep_rpa.npz"
compute_esp = true
compute_polarizability = true

A requested property that cannot be computed fails the run unless allow_partial_npz = true. Read the archive with numpy.load:

import numpy as np
d = np.load("h2o_pdep_rpa.npz")
print(d.files)

Keys written for water/cc-pVDZ with the input above (N atoms, nao = 24):

KeysShape
coords, atomic_numbers(N, 3), (N,)
mo_coeffs, density_matrix(nao, nao)
orbital_energies(nao,)
pdep_eigenvectors(naux, M), M retained eigenvalues
orbital_centers, orbital_spreads(nmo, 3), (nmo,)
density_second_moment, dipole(3, 3), (3,)
esp_atoms, electric_field(N,), (N, 3)
esp_surface, esp_points(npts,), (npts, 3); compute_esp_surface = true only
alpha_tensor, alpha_ct(3, 3)
alpha_atomic(N, 3, 3)
hirshfeld_charges, lowdin_charges, mulliken_charges, chelpg_charges, resp_charges(N,)
c6_freqs, c6_weights(n_quad,)
alpha_atomic_dynamic(N, n_quad, 3, 3)
alpha_ct_dynamic(n_quad, 3, 3); c6_source = "pdep" only
c6_iso, c6_aniso(N, N), (N, N, 3, 3)
c6_molecular_iso(1,)
c6_partition, c6_sourcebyte strings; decode with .tobytes().decode()

mo_coeffs, orbital_energies, orbital_centers and orbital_spreads are written for restricted references only. c6_iso and c6_aniso are per-atom pair tensors under a partition convention. They do not sum to the molecular C6; read c6_molecular_iso. alpha_ct + alpha_atomic.sum(0) == alpha_tensor. The definitions of alpha_atomic and alpha_ct are in the compute_alpha_atomic row of the input reference.

PDEP eigenpotential cubes

Setting export_eigpot_prefix in [rpa] writes <prefix>_eigpot_NNN.cube for the leading export_eigpot_count eigenpotentials (default 10; the count is capped at the number retained). cube_spacing (default 0.2 Bohr) and cube_margin (default 4.0 Bohr) set the grid. examples/benzene-pdep-rpa-export.toml is a complete input.

Golden paths: ferric in applications

End-to-end workflows for real questions, written so an agent can execute them without reading the rest of the repo. Each one states what it answers, what it cannot, and how to tell it went wrong.

Read For agents first if you are automated. It lists the failure modes that cost the most time. Individual steps live in Recipes.


Golden path A — Reaction energetics of a cation cascade

Answers: which intermediate is the bottleneck, and which branch point controls product selectivity.

Worked example: terpene synthase carbocation cascades (geranyl → linalyl → α-terpinyl → product). The same shape applies to any closed-shell cation mechanism.

Step 0 — gate before you spend hours

pip install ferric
git clone https://github.com/mgoldey/ferric && cd ferric   # examples/, testdata/, scripts/
ferric examples/water-rhf.toml    # expect converged = true, energy = -74.9631468000

Don't skip this, and don't build from source first. The wheel installs in about a minute, and a source build takes about 10 minutes. If the install is broken, every later failure will look like a chemistry problem. The clone is needed because the wheel doesn't ship examples/, testdata/ or scripts/.

Run the study itself under scripts/ferric-limited --max=8G --high=7G --. These can be multi-hour jobs, and an OOM kill leaves a truncated log with no error message.

Step 1 — build the species, and check parity

Generate starting geometries however you like; RDKit ETKDG + MMFF is fine. Use force fields for geometry seeds only, never for a reported energy.

Then, before any QM, verify electron parity for every species:

n_elec = sum(ATOMIC_NUMBER[sym] for sym in symbols) - charge
assert (n_elec % 2 == 0) == (multiplicity % 2 == 1), (
    f"{name}: {n_elec} electrons cannot have multiplicity {multiplicity}"
)

A cation is charge=1, multiplicity=1. Getting this wrong is the most common first failure. ferric catches it with a clear message, but checking in your own loop fails faster and names the species.

Check geometry as well as parity. A structure can have a correct formula and still be physically impossible. Measured: a hand-built C10H17+ passed parity (76 electrons, singlet) while carrying a 0.902 A C-H contact, shorter than a real bond, and a hydrogen 1.030 A from a second carbon. The SCF stalled at 37 iterations, which is the correct behaviour for overlapping nuclei.

# formula check and geometry check fail on different mistakes; keep both
assert (n_elec % 2 == 0) == (multiplicity % 2 == 1)   # bad stoichiometry
assert min_interatomic_distance(xyz) > 0.9            # gross overlap only (your own helper, Angstrom)
# The failure above PASSES that screen (closest contact 0.902 A): its H sat
# ~1 A from two carbons at once. Also reject any H bonded to two heavy atoms:
assert max_heavy_neighbours_of_hydrogen(xyz, cutoff=1.3) <= 1

Step 1b: relax with xtb before any DFT

xtb is the middle tier of the funnel. GFN2-xTB fixes a bad structure in seconds:

LD_LIBRARY_PATH=~/.local/lib/x86_64-linux-gnu \
  xtb mol.xyz --opt --chrg 1 --uhf 0 --gfn 2
# -> xtbopt.xyz

A local install needs LD_LIBRARY_PATH. Without it, xtb fails with libxtb.so.6: cannot open shared object file; the install itself is fine.

Measured on the same molecule, method and basis:

hand-built geometry : closest contact 0.902 A -> SCF Stalled @37, E = -389.5110690
xtb-relaxed (54 its): closest contact 1.084 A -> SCF converged,   E = -390.3794234

The relaxed structure is 0.868 Ha (545 kcal/mol) lower and converges; the hand-built one does not.

Step 2 — optimize each intermediate

[molecule]
xyz = "alpha_terpinyl.xyz"
charge = 1
multiplicity = 1

[basis]
name = "6-31g"           # def2-SVP did not fit on a workstation here; see below

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

[dft]
functional = "PBE"

[scf]
max_iter = 400           # 173 iterations was needed; the default is 200
energy_conv = 1e-7       # a loose, reachable bound; see below
# density_conv defaults to 1e-6 and is the criterion that does the real work.
# Tighten it if you need more than the energy; see below.

[optimize]
max_steps = 60

These settings follow from the measurements below. Don't raise the basis or tighten energy_conv without measuring one species first.

Delocalised cations need iterations, and a reachable energy_conv. Measured on the same 27-atom cation, changing only these two knobs:

max_iter 100, energy_conv 1e-8  ->  MaxIter @100,  29 min, 3.30 GB, no result
max_iter 400, energy_conv 1e-7  ->  CONVERGED @173, 4.9 min, 1.81 GB

173 iterations is normal for a delocalised carbocation; the default cap of 200 is not sized for this. energy_conv = 1e-8 was unreachable: under density fitting the energy change floors with naux, so a dE bound tighter than the RI noise floor is never met and the run spends its whole iteration budget on it. density_conv is the criterion that converges the density. Leave the dE bound loose enough to be satisfiable.

The exit reason is diagnostic. Stalled means the SCF could not make progress: suspect the geometry. MaxIter means it was progressing and ran out of iterations: raise the cap.

Set density_conv as well as energy_conv. E_HF is variational and tolerates a loose density; anything depending linearly on the MO coefficients does not. A correlation energy computed from an unconverged density can look reproducible and still shift when anything upstream changes, because the density was never converged.

Checkpoint every species as it finishes. A crash at species 6 of 8 should cost one species, not the whole run.

The method and basis decide whether the study is feasible. Measured on the same 27-atom cation, single point (both the basis and the functional differ between the two rows, so this does not isolate the cost of the basis):

def2-SVP / B3LYP : >22 min, 6.04 GB, SIGKILLed before converging
6-31G   / PBE    :  4.9 min, 1.81 GB, CONVERGED

6-31G/PBE used a third of the memory and finished. Don't plan a def2-SVP cascade on a workstation without measuring one species first.

Treat the optimization time as an estimate. An optimization takes tens of gradient steps. Estimate: at ~5 min per 6-31G single point, 30–60 steps would take roughly 2.5–5 hours per species if each gradient step costs about as much as a single point. Neither a gradient step nor a full optimization of this system has been timed. Later steps start from the previous density and may converge faster than a cold start. Time one optimization before budgeting a cascade.

Use a release build. Measured: the same cation at B3LYP/def2-SVP ran for more than 34 minutes on a debug binary, sharing the machine with one other job, without converging a single point. The wheel and cargo build --release are both release builds.

A 27-atom cation at def2-SVP needs ~6 GB. Budget the study with that in mind. Running eight species of that size at once exhausts a workstation. Always wrap the run in scripts/ferric-limited --max=8G --high=7G --. For agents explains why the in-process budget alone is not enough.

Size it before you start. Measured: the tert-butyl cation (C4H9+, 101 basis functions at def2-SVP) is a single point that takes seconds. A C10 cascade cation (C10H17+) has 225 basis functions. Estimated at N^3.5 scaling, that is about 16x the single-point cost. That is cheap for an energy, but an optimization multiplies it by the step count, so the optimizations of the C10 species dominate the cost of the study. Run one species end to end and time it before you queue eight.

Step 3 — confirm the ordering survives the functional

Run single points on the optimized geometries with at least two more functionals, for example B3LYP and wB97X-V (both available; wB97X-V resolves to libxc's HYB_GGA_XC_WB97X_V), at a basis that fitted in Step 2.

If the functionals disagree on the ordering of intermediates, report the disagreement. Do not average them or pick the one that matches your hypothesis. A functional-dependent ordering is a finding about the system.

Step 4 — properties at the key intermediate

Hirshfeld and Löwdin charges, the ESP at the nuclei, and the static polarizability of the π-stabilized cation. From Python these are ferric.hirshfeld_charges, ferric.lowdin_charges and ferric.esp_at_atoms.

Cost warning: the polarizability comes from ferric-rpa and is an RPA-level calculation. Budget it separately from the DFT run. It can cost more than the optimization before it.

Step 5 (optional) — barriers

Intermediates alone give you a thermodynamic profile. If you need barriers, ferric has a Python-only transition-state toolchain. None of it is wired to the CLI's method.task:

CallWhat it doesScope
ferric.run_saddle(mol, basis, xc=...)P-RFO search for a first-order saddle pointClosed shell only (multiplicity 1), HF or KS. It raises if the start has no negative Hessian mode, so start from a guessed TS, not a minimum. The Hessian is built twice by central differences and Bofill-updated in between: 2(6N+1) + (steps+1) gradients.
ferric.run_irc(mol, basis, mode=...)Follows the reaction path downhill in both directions, from the saddle to the two minima it connectsClosed shell only. Pass SaddleResult.imaginary_mode as mode. MEASURED ~71 gradients per direction on NH3 inversion.
ferric.run_frequencies(mol, basis, reference=..., xc=...) (CLI: task = "frequencies")Harmonic frequencies from the analytic Hessian where it applies (RHF, UHF, ROHF, and closed-shell KS LDA/GGA through the moving-grid KS Hessian, all under the default hessian="auto"), otherwise finite differences of the analytic gradient (6N gradients). Negative entries are imaginary modes. .normal_modes gives the vectors.RHF/UHF/ROHF and their KS variants. Check .asymmetry to judge whether the step size suited the system. dispersion="d3bj" or "mbd" (CLI: [dft] dispersion) adds the dispersion Hessian on RKS, UKS and ROKS.

One imaginary frequency is necessary but not sufficient for a transition state: a methyl rotor also gives one. Use run_irc to confirm that the saddle connects the two intermediates you meant.

What this cannot tell you

State these in any write-up. They define the scope of the claim.

  • Gas-phase cluster models. There's no enzyme environment unless you add QM/MM (QM/MM).
  • Analytic Hessians for RHF, UHF and ROHF only. Their frequencies use the analytic Hessian (RHF and UHF also with an ECP or fixed external charges or field; ROHF with neither); KS-DFT (ROKS included), implicit solvent and polarizable embedding frequencies, and every TS search, use finite differences of analytic gradients (Step 5). That costs 6N gradients per Hessian, so a C10 cation Hessian is hundreds of gradient evaluations. Without Step 5 you have intermediate energies but no barriers, and kcat depends on barriers.
  • No thermochemistry. There's no entropy, enthalpy or free-energy correction anywhere, from the CLI or Python. You can compute a zero-point energy yourself from the frequencies (½Σhν over the real modes). Python's FrequencyResult doesn't provide one.
  • Relative energies only. Absolute totals carry basis-set and functional errors far larger than the differences you are interpreting.

Reading the output

SignalMeaning
converged = trueTrust the energy. Absence of this line is not success.
converged = falseAn energy is still printed. It is not a result.
exit Some(Stalled)The SCF could not progress. Suspect the geometry: check the minimum interatomic distance and relax with xtb.
exit Some(MaxIter)It was progressing and ran out of iterations. Raise max_iter (173 is normal for a delocalised cation) and check energy_conv is reachable under DF.
exit Some(Diverged)The energy climbed for several iterations in a row. Suspect the geometry or the charge/multiplicity before the solver.
charge/multiplicity errorYour .xyz atom count is wrong. Read the arithmetic it prints.
Disagreement with another code ~1e-5 HaCheck the grid: ferric (75,110) vs PySCF ~(75,302). Scales with atom count.
Larger KS-DFT disagreement that grows with sizeferric density-fits Coulomb by default in KS-DFT. See Recipes §6.
A correlation energy that moves when nothing physical changeddensity_conv was never set.

Before publishing any number

Check the capability's grade in Capabilities and validation. A method being available from the CLI doesn't mean its numbers are production-grade. The grades are Proven, Proven (narrow), Smoke and Spike; a few kinds are not graded.

ferric for agents

Read this first if you are an automated agent driving ferric. It's a routing page: what exists, where each thing is documented, and which mistakes cost the most time. The linked pages hold the details.

Start here

Don't build from source unless you need to. The prebuilt wheel installs in about a minute. A source build takes about 10 minutes, mostly compiling ferric.

pip install ferric
git clone https://github.com/mgoldey/ferric && cd ferric   # for examples/, testdata/, scripts/
ferric examples/water-rhf.toml

Expect converged = true and energy = -74.9631468000 Hartree. If you don't get that, stop and fix the install (Installation). Nothing else will work, and the failures will be confusing.

pip install puts a ferric command on PATH that runs the same CLI as the source build. The wheel holds only the compiled library and that command. The examples/, testdata/ and scripts/ directories need the clone.

Which wheel

ChannelWhatHow
PyPIBuilt by the Wheels workflow on a v* tag. So far only pre-releases (0.1.0rc*) exist, and pip and uv pick them when there's no final release.pip install ferric
GitHub nightlyBuilt every night (53 10 * * * UTC) from main. It's a workflow-run artifact, not a PyPI upload.Download it from the Wheels workflow run, then pip install ./<wheel>

Use the nightly artifact when you need a fix that has landed on main but hasn't been tagged, instead of building from source.

Build from source when you are changing ferric or need MPI. MPI builds are source-only (see Installation).

Capability → entry point

You wantUseWhere it's documented
Which methods exist, with which tasks and validation grade—Capabilities and validation
An energy from a TOML fileCLI, method.kindRecipes §1, input reference
An ion, radical or metal center[molecule] charge, multiplicity (Python: on Molecule.from_xyz)Recipes §2
An optimized geometrymethod.task = "optimize"Recipes §3
Harmonic frequenciesmethod.task = "frequencies", ferric.run_frequenciesAnalytic Hessian for RHF/UHF/ROHF and closed-shell KS LDA/GGA (the moving-grid KS Hessian, chosen by the default hessian = "auto"), otherwise finite differences of analytic gradients; examples/water-frequencies.toml
A transition state and its reaction pathferric.run_saddle, ferric.run_irc (Python only, closed shell)Golden paths Step 5
QM/MM embeddingferric.QmmmSystem, ferric.run_qmmm, CLI [qmmm]QM/MM
A machine-readable record of a run<input>.ferric.jsonl, written by defaultRun logs
Python instead of TOMLthe ferric modulePython API

Before you trust a number

Check the grade. Not every capability is equally validated. A method being available from the CLI doesn't mean its numbers are production-grade. Read its row in Capabilities and validation before quoting a value.

Check convergence, not the exit code. converged = true (CLI) or .converged (Python) is the signal. A run that hit max_iter still prints an energy. In the run log, read run_end.converged, and for correlated methods read the result record (Run logs).

When comparing to another code, check density fitting and the grid. Recipes §4 covers both.

Failure modes that cost the most time

  1. Charge/multiplicity parity. An odd electron count needs an even multiplicity. The error message prints the arithmetic and the rule, and it means your .xyz or your charge is wrong. ferric can handle ions.
  2. Debug instead of release. A debug binary is far slower. Measured: a 27-atom cation didn't converge a single point in 34 minutes on one. If a job seems hung, check which binary is running. cargo run --release --bin ferric and the wheel are both release builds. ./target/debug/ferric is not.
  3. MPI is a workspace feature, not a CLI one. ferric-cli has no mpi feature of its own, so -p ferric-cli --features mpi fails. The working build is cargo build --release --workspace --features mpi.
  4. Threading. Don't set OPENBLAS_NUM_THREADS above 1. The CLI and import ferric pin OpenBLAS to one thread when the variable is unset and honour an explicit value, and cargo sets it to 1 only when it is unset (an exported value wins). Multithreaded BLAS under ferric's rayon parallelism can crash (LU routines) or oversubscribe the machine, so the setting affects correctness as well as speed.
  5. Under-converged density. energy_conv alone doesn't converge the density. Correlation energies and properties inherit the full first-order density error. E_HF doesn't, because it's variational. Set density_conv.
  6. Serialize QM jobs. Two concurrent ferric runs slow each other by more than half. Measured: a pair of them pushed a single point that normally takes seconds past a 15-minute timeout. Each run wants all the cores and several GB. Run one at a time.

Memory: the budget predicts, the cgroup enforces

ferric works out a memory budget from [memory] budget_gb, FERRIC_MEM_BUDGET_GB, or, if neither is set, 0.8 × available RAM. The CLI prints that budget at startup and installs it as one shared, debited pool for the whole process: the three-index RI tensors, the DFT grid's AO cache and the large tensors of the MP2, RPA, GW and CC methods reserve their bytes from it, so two allocations alive at the same time cannot each claim the whole budget. From Python, memory_budget_gb= sets the same per-allocation limits but installs no shared pool, so each check compares its own allocation with the whole budget. In both, an allocation that does not fit is spilled to disk, recomputed on demand, or refused with an error naming it, depending on the allocation (see Sharp bits).

The budget isn't a cap on process memory (RSS). It covers the dominant tensors, not every allocation. Basis-sized matrices, integral engines (for example libint2's C++-side pools), BLAS and per-thread scratch and allocator overhead aren't charged, so treat the printed number as a floor on what the process needs. Lowering FERRIC_MEM_BUDGET_GB changes how the charged work is split into blocks. It doesn't shrink the uncharged part.

Size the budget from a gradient run. Measured on benzene/cc-pVDZ/PBE/RI-J: the SCF alone peaked at 0.616 GB, and the SCF plus gradient at 3.033 GB. The KS gradient holds the AO second derivatives on the grid. A budget sized from an energy run under-provisions a geometry optimization by roughly 4x, and the first optimization step is where it fails.

The cgroup is the only hard ceiling. On Linux with systemd:

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

An unbounded overshoot triggers the system-wide OOM killer, which can kill unrelated processes. Inside a cgroup, only the job dies. dmesg | grep -i oom confirms a kill. A truncated log doesn't prove one. As a rule of thumb, plan on ~6 GB for 27 atoms at def2-SVP.

Telling a running job from a dead one

  • Look for evidence of work, not a process name. pgrep -f also matches your own grep, a bash -c wrapper, or a defunct entry at 0% CPU. Sort by CPU instead: ps -eo pid,stat,pcpu,rss,etimes,comm --sort=-pcpu | head.
  • A log that has stopped growing doesn't mean the job is dead. stdout is buffered, so a long QM run can legitimately print nothing for tens of minutes. The run log (Run logs) is flushed record by record, so watch it instead.
  • Terminated usually means timeout(1), not the OOM killer. Check whether a timeout wrapped the command before you look in dmesg.
  • A grep filter that matches nothing looks the same as "the job produced nothing". Test the filter against known-good output first.

Scope

ferric is a quantum chemistry engine: energies, gradients, finite-difference Hessians and properties. It isn't a protein-engineering tool, a docking program or an MD code. The QM is the product.

Its analytic Hessian covers RHF, UHF, ROHF and closed-shell KS LDA/GGA (other frequencies are finite differences of the gradient), and it has no thermochemistry (entropy, enthalpy, free energy). Transition-state search and IRC are Python-only and closed-shell only (Golden paths Step 5). If a task asks for something ferric can't do, such as designing mutations or predicting ΔΔG, say so instead of approximating it with something adjacent.

Methods overview

What exists, grouped by family. Maturity varies a great deal between these. Check the grade and its anchors on Capabilities and validation before trusting a number. To go from a task to a method, start with Choosing a method.

FamilyMethodsPage
SCF and DFTRHF, UHF, ROHF; KS-DFT (LDA, GGA, hybrid, range-separated, VV10, SCAN/r2SCAN/TPSS); D3(BJ) and MBD@rsSCS dispersion; IEF-PCM and COSMO solvation; gradients, optimization, frequencies (analytic Hessian for RHF/UHF/ROHF and closed-shell KS LDA/GGA; finite differences otherwise), TS search and IRCSCF and DFT
MP2RI-MP2, attenuated (erfc, terfc), SCS, SCS-MP2(2terfc), MP2-V, RS-MP2 + LR-RPA, OO-RI-MP2, MP3, Laplace MP2 and SOS-MP2; local approximation of RI-MP2The MP2 family
Coupled clusterCCD, CCSD, CCSD(T), LinLCCD (hh, drivers-only, full; exact or local); double hybrids B2PLYP, DSD-PBEP86, ωB97X-L-VCoupled cluster
CAS-CIfull CI in an active space on a closed-shell RHF reference; lowest root; Rust library onlyCAS-CI
ResponsePDEP-RPA, dRPA by the Riccati solve (exact or local), G0W0, COHSEX, evGW0, evGW, BSE-TDA, TDA/TDDFT, polarizabilities and \( C_6 \)RPA, GW and excited states
Electron transfercDFT, \( H_{ab} \) couplings (Python and Rust, no CLI)Constrained DFT
Periodic systemsGamma-point and k-point RHF/UHF/ROHF, RKS/UKS/ROKS, MP2, dRPA; analytic forces and stress; dense AFT or RS-GDF J/KPeriodic systems

QM/MM embedding is on its own page: QM/MM.

Exact and local correlation

A correlated calculation is two choices: the method (what is computed: RI-MP2, dRPA, LinLCCD) and the approximation (whether, and how, its amplitudes are truncated). ferric keeps them apart. method.kind (and the Python function) names the method, and the method is computed exactly. A local approximation is asked for explicitly, with the CLI's [local] section or the Python local= and eps= keywords, and only rimp2, drpa and linlccd have one.

The one local scheme is the amplitude threshold: in the Boys-localized basis, a pair amplitude whose localized integral is at or below eps is dropped (single threshold, Wang et al. 2023). Three facts govern its use:

  • eps is part of the model. It has no default and must be written, and every printout and run-log record of a local run carries it, with the fraction of amplitudes kept ("local": null marks an exact run). Report it with any number you quote.
  • eps = 0 is the exact method. It keeps every amplitude and reproduces the exact energy (RI-MP2 to ≤ 1e-9 Ha; dRPA to the canonical plasmon formula; LinLCCD of the same variant), which is how each local path is anchored.
  • The error is one-sided and grows faster than linearly in eps: dropped amplitudes give less correlation energy, measured with the same sign at every point, and each decade of eps raises the error by more than a decade (the fraction of amplitudes dropped grows with it). MP2 has Hylleraas stationarity; dRPA does not, so its error is first order in the dropped amplitudes and the one-sidedness is measured, not guaranteed.

Riccati, plasmon and PDEP are not approximations of each other: they are algorithms for the same exact dRPA. The drCCD Riccati solve at eps = 0, the plasmon formula and full-rank PDEP agree to ≤ 2.6e-14 Ha (H2/STO-3G and water/6-31G, PDEP at 64 quadrature points). Which one to use is a cost question: see RPA, GW and excited states.

Formal scaling with system size N, for orientation (these are the textbook exponents, not measured timings): SCF and DFT N⁴ for exact four-centre integrals, lower with density fitting and screening; RI-MP2 and its variants N⁵; CCSD N⁶; (T) N⁷.

Infrastructure

Shared machinery underneath all of the above:

  • Screening: Schwarz bounds on every four-centre path, CSB/CSAM (Thompson & Ochsenfeld 2017) as options, LinK exchange (Ochsenfeld, White & Head-Gordon 1998). QQR (Maurer, Lambrecht & Ochsenfeld 2012) is implemented and validated as a bound but not used in production.
  • Spherical and Cartesian basis support, with BSE-JSON and Gaussian-94 parsers and a set of bundled orbital, RI, JK and ECP bases.
  • einsum!: a tensor-contraction macro that routes contractions through BLAS3 GEMMs, used throughout the CC and MP3 code.
  • Memory budgets: allocation ceilings that refuse an oversized job with a named breakdown instead of letting it be OOM-killed.
  • Python bindings (pyo3) and a TOML-driven CLI. Not every method has both; Capabilities and validation says which.

Properties

ESP at nuclei and on the molecular surface, electric field, static and atom-partitioned polarizabilities, Mulliken, Löwdin, Hirshfeld, CHELPG and RESP charges, density matrices, and NPZ export of ML-ready features.

Negative results

Known limits and measured negatives are kept in one place, Capabilities and validation: known limits, so a correction has one row to change. Two matter when picking a method:

  • TDHF/RPAx \( C_6 \) stays about 63% low regardless of gap. Use that kernel for static polarizabilities only.
  • Local MP2: the integral-direct local MP2 (rimp2 with [local] integral_direct = true) is measured sub-quadratic (about N1.24 erfc, N1.4 Coulomb) on alkanes C20–C48; it is at about parity with exact RI-MP2 at C20 and about 6× faster at C32. Local MP2 without integral_direct makes no scaling claim. See The MP2 family.

The AO-sparse Laplace SOS-MP2 truncation is not a negative: the radius it needs does not track the molecular diameter. See the MP2 page.

SCF and DFT

Ground-state self-consistent field methods, their nuclear gradients, and the things built on them: geometry optimization, harmonic frequencies, transition-state search, implicit solvation and dispersion correction. The [scf] and [dft] keys are in Input file; grades are on Capabilities and validation.

Hartree–Fock

What it is. RHF (closed shell), UHF and ROHF (open shell), with DIIS, Schwarz screening, and a choice of direct, LinK, density-fitted (RI-J / RI-K) or seminumerical (COSX) Fock builds (see Choosing how exchange is built below). The open-shell solvers add per-spin DIIS, a virtual-block level shift, augmented-Hessian Newton, and Maximum-Overlap-Method (MOM) orbital tracking for near-degenerate cases (Gilbert, Besley & Gill 2008). These exist because specific systems failed without them.

Run it. method.kind = "rhf" / "uhf" / "rohf" (examples/water-rhf.toml, examples/h_uhf.toml); Python ferric.run_rhf, run_uhf, run_rohf. Spin and charge are set on the molecule (Molecule.from_xyz(path, charge, multiplicity)), not on the solver call.

Defaults. Exact four-index J and K (no density fitting) unless [scf] df_j_aux / df_k_aux are set. Convergence gates on the density: density_conv = 1e-6 by default; energy_conv (default 1e-3) is a sanity bound, not the convergence target; a value below the estimated noise floor of the energy change draws a warning (see sharp bits).

Accuracy. Proven (rhf, uhf, rohf).

Kohn–Sham DFT

What it is. Kohn–Sham DFT through libxc: LDA, GGA, hybrid and range-separated hybrid functionals (for example PBE, B3LYP, ωB97X-V), VV10 nonlocal correlation, and the SCAN / r2SCAN / TPSS meta-GGAs (τ-dependent; no density Laplacian). The solvers cover RKS, UKS and ROKS.

Run it. method.kind = "ksdft" with [dft] functional = "PBE" (examples/water-wb97xv.toml, examples/benzene-dfb3lyp.toml); Python ferric.run_dft or run_ksdft. The two Python entry points are closed shell (RKS) only. In the CLI, ksdft on a molecule with multiplicity > 1 runs UKS, and kind = "uhf"/"rohf" with [dft] functional run UKS/ROKS. Open-shell KS is also the reference inside pdep-rpa and gw ([rpa] xc with multiplicity > 1), and in Python through run_frequencies(reference="uhf", xc=...) and QM/MM (run_qmmm(method="uks")); see Capabilities and validation.

Defaults.

  • Functional: LDA if [dft] functional is omitted.
  • Grid: Becke partitioning of atom-centred Treutler–Ahlrichs radial × Lebedev angular grids, up to 75 × 110 points per atom, pruned by default (grid_prune = "nwchem-radial": NWChem angular regions, and 50 radial shells for H and He, 66 for Li–Ne, 75 for heavier atoms) with a 1e-14 weight cutoff (grid_weight_cut, ORCA's WeightCut). That is 42-47% fewer points than the flat 75 × 110 grid on organic molecules, with energies within 2e-7 to 4e-6 Ha of it on PBE/def2-SVP (the flat grid's own error against a 99 × 590 grid is 4.5e-4 to 3.3e-3 Ha on C10–C32 alkanes).
  • The flat 75 × 110 grid is grid_prune = "none" with grid_weight_cut = 0. "nwchem" prunes the angular grid only (about 23% fewer points, examples/water-pbe-pruned-grid.toml); "defgrid1"–"defgrid3" are ORCA 6.1's DEFGRIDs, with their own sizes: fewer radial shells than the default (H 30/35/44, Li–Ne 38/44/53, Na–Ar 46/54/63 against 50/66/75) but more angular points (peak orders 194/302/590 for heavier atoms and 110/194/434 for H and He, against 110). defgrid2, ORCA's default, moved PBE/def2-SVP energies by 1e-3 to 4e-3 Ha from the flat grid. Setting only grid_radial / grid_angular keeps the default scheme at that size. Optimization, frequency (including the analytic moving-grid Hessian) and TDDFT runs use the grid the SCF ran on.
  • SCAN is the exception to "pruning costs nothing". On water/6-31G its gradient error against a 99 × 590 grid is 2.1e-4 Ha/Bohr on the flat grid and 5.6e-4 on the default, because the hydrogens lose radial shells. r2SCAN (3.7e-4 flat, 3.6e-4 default) and PBE (unchanged) are not affected. For SCAN, use grid_prune = "nwchem" or a larger grid.
  • Density fitting: RI-J and RI-K are on by default for ksdft and run_dft (def2-universal-jkfit), while rhf is exact by default. In Python, df_j_aux="" selects exact Coulomb. The fitting error grows with size: measured at PBE/STO-3G against exact J it was 0.28 kcal/mol for water, 1.16 for benzene and 9.5 for a 71-atom drug molecule. Compare with an exact-Coulomb code only after turning fitting off, or at matched fitting.
  • Meta-GGAs get an automatic 0.5 Ha virtual-block level shift (ramped to zero at convergence) when you set none, because τ amplifies grid noise and plain DIIS limit-cycles.

Accuracy. ksdft is Proven. SCAN/r2SCAN/TPSS energies are pinned against PySCF (ferric-scf/tests/dft_scan.rs); the SCAN/r2SCAN closed-shell gradients against PySCF with grid response (dft_gradient_mgga.rs). TPSS gradients have no PySCF pin. Other meta-GGA variants (deorbitalized, +VV10, hybrid meta-GGA) are refused, not approximated. The \( V_{xc} \), \( f_{xc} \), VV10, Becke-weight and gradient formulas are derived in DFT derivations.

Dispersion correction: D3(BJ)

What it is. Grimme's D3 with Becke–Johnson damping, a post-SCF pairwise correction, implemented natively in Rust: the two-body term, plus the Axilrod–Teller–Muto three-body term when its scale s9 is nonzero. s9 defaults to 0 (off), the reference default for a named functional (simple-dftd3 without --atm).

Run it. [dft] dispersion = "d3bj" on method.kind = "ksdft" (examples/water-pbe-d3bj.toml); "d3bj(b3lyp)" borrows another functional's fitted damping parameters. Python: run_dft(..., dispersion="d3bj"), or ferric.d3bj_energy(mol, functional, s9=0.0) for the correction alone; s9=1.0 adds the three-body term. The CLI and run_dft(dispersion=...) use s9 = 0.

Scope. The damping parameters are fitted per functional, so the key is refused on any method other than ksdft. Tabulated functionals, each equal to simple-dftd3 1.6.0's rational-damping entry: BLYP, BP86, B97-D, PBE, PBE0, revPBE, B3LYP, TPSS, TPSS0, B2PLYP, BHLYP, SCAN, r2SCAN, ωB97X. M06-2X has no D3(BJ) fit (only zero-damping D3(0)) and is an error. task = "optimize" works (the D3 gradient is implemented; ferric-d3/tests/gradient_vs_fd.rs), and so does task = "frequencies" on RKS, UKS and ROKS: see Frequencies with dispersion.

Accuracy. Energy and analytic gradient agree with simple-dftd3 1.6.0 with the three-body term off and on, on nine molecules from water to benzene and CHBr3: 1.2e-13 Ha and 5.5e-13 Ha/Bohr on the totals, 2.6e-15 Ha on the three-body part (ferric-d3/tests/vs_reference_dftd3_atm.rs). The three-body term is 0.1% of the two-body energy at benzene and grows with size. Unlike simple-dftd3, no real-space cutoff is applied (it drops triples beyond 40 Bohr), and the triple sum costs O(N³).

Dispersion correction: MBD@rsSCS

What it is. The many-body dispersion energy of Ambrosetti et al. 2014 (range-separated self-consistent screening), added post-SCF. Its per-atom inputs are Tkatchenko–Scheffler free-atom α, C6 and R_vdW scaled by Hirshfeld volume ratios v_A / v_A^free of the converged SCF density; the free-atom volumes come from live free-atom SCFs in the same basis and SCF settings, solved for the isolated atom: point charges, external fields, implicit solvent, polarizable sites and cDFT constraints of the molecular run are not applied to it. The model itself is described under Polarizabilities and dispersion coefficients.

Run it. [dft] dispersion = "mbd" on a Kohn–Sham SCF (examples/water-pbe-mbd.toml) uses the β published for [dft] functional (PBE 0.83, PBE0 0.85, HSE06 0.85); "mbd(pbe0)" uses another functional's β. Any other functional is an error, not a default β. The printout gives E(KS-DFT), E(MBD@rsSCS) with β and the functional, and the corrected total; the JSON run log's run_end record carries the corrected total as energy, with scf_energy and a dispersion object (model, params, energy, beta, volume_ratios) in extra (D3(BJ) runs log the same object without beta and volume_ratios). UKS/ROKS energy runs, which write no run_end record, write a dispersion record with the same fields. Python: run_dft(..., dispersion="mbd"), which also reports DftResult.volume_ratios.

Gradient. task = "optimize" on an RKS, UKS or ROKS reference and run_dft(with_gradient=True) add the exact analytic MBD@rsSCS gradient: the explicit dependence on the nuclear positions, and the dependence through the Hirshfeld volumes, including how the SCF density itself responds to the displacement. That response is the orbital relaxation, obtained from one coupled-perturbed Kohn–Sham (Z-vector) solve per gradient; it costs about as much as a few SCF iterations. On a UKS reference the solve is coupled across the α and β orbital rotations (Coulomb couples the spins; exchange and the XC kernel are spin-resolved), and the orthonormality of each spin's occupied orbitals enters separately. On a ROKS reference the solve runs over the three rotation blocks of the shared orbitals (closed→virtual, open→virtual, closed→open) with the exact restricted-open-shell orbital Hessian, and the closed and open orbitals are kept orthonormal as one set, which adds a closed–open cross term to the orthonormality contribution. The volumes are integrated on a lattice anchored to the molecular centroid, and its motion is part of the gradient. Against finite differences of the full SCF + MBD calculation at 6-31G the gradient agrees to 6e-9 Hartree/Bohr for H2O with PBE, PBE0, HSE06 and PBE with RI-J, and to 9e-10 for NH3; the orbital relaxation alone is 1.0e-5 for H2O (11.5% of the largest MBD component). For UKS doublets and a triplet (NH2, OH, O2 at 6-31G with PBE, PBE0 and HSE06) it agrees to ≤ 1.9e-9, and to 6e-9 for OH with PBE + RI-J and PBE0 + RI-JK, against an orbital relaxation of 3e-6–1e-5. For ROKS doublets and triplets (HCO, NH2, CH2, O2 at 6-31G) it agrees to ≤ 2.1e-11 with PBE and PBE0, ≤ 4.5e-10 with HSE06 and 7.5e-9 for HCO with PBE + RI-J, against an orbital relaxation of 2.7e-6–9.8e-6. For a linear radical whose state breaks the cylindrical symmetry (²Π: OH, NO, CH), a rotation of the electrons about the molecular axis is an orbital rotation that leaves the energy unchanged, so the orbital Hessian has a null mode; only the anisotropy of the XC grid gives it a small eigenvalue of either sign. The UKS and ROKS Z-vectors detect it on linear molecules and solve on its complement, which is exact for a property that is symmetric about the axis. On OH, NO and CH (STO-3G and 6-31G, PBE and PBE0) the gradient agrees with finite differences of the full pipeline to 7e-12–5e-11 Hartree/Bohr. A state with any other negative or near-null orbital-Hessian direction is still refused (the UKS-PBE CH state is a saddle point and is refused). The relaxation term needs an LDA, GGA or hybrid-GGA functional without VV10, no implicit solvation, polarizable embedding or cDFT constraints, and integer aufbau occupation (no MOM). With COSX exchange the overlap fit must be off (cosx_overlap_fit = false); the UKS-PBE0 + COSX gradient on NH2/STO-3G agrees with finite differences of the full pipeline to 2.3e-11. Other setups are refused rather than given an approximate gradient. Python's run_dft is closed-shell only; open-shell frequencies with MBD@rsSCS run through the CLI or run_frequencies.

Frequencies with dispersion

What it is. Harmonic frequencies on the dispersion-corrected surface E(KS) + E(disp), on an RKS, UKS or ROKS reference. The Hessian is the central difference of the corrected analytic gradient: at each of the 6N displaced geometries the SCF of that reference is converged, the KS gradient and the dispersion gradient are evaluated there, and their sum is differenced. For MBD@rsSCS the dispersion gradient is the exact one above, with the Z-vector of the reference solved (RKS, UKS or ROKS), so the response of the Hirshfeld volumes to the displacement, through the SCF density, is in the Hessian. There is no analytic Hessian on this path: [frequencies] hessian = "analytic" is an error and "auto" runs finite differences.

Run it. [dft] dispersion with method.task = "frequencies" on a KS SCF: ksdft, or rhf/uhf/rohf with [dft] functional. The printout gives the corrected energy, E(KS-DFT) and the dispersion energy at the input geometry, and the JSON run log gets a dispersion record with the same fields as an energy run. Python: run_frequencies(mol, basis, xc="PBE", dispersion="d3bj"), which reports .e_dispersion and the corrected .energy; reference="uhf" or "rohf" selects UKS or ROKS.

Accuracy. For PBE/STO-3G water, the D3(BJ) part of the Hessian agrees with 4-point second differences of the D3(BJ) energy to 5.3e-9 Hartree/Bohr² (largest element 1.9e-5), and the resulting frequencies with those of the KS Hessian plus that independent D3 Hessian to 1e-5 cm⁻¹. The MBD@rsSCS part agrees along three fixed directions with second differences of the full SCF + MBD energy to 1.5e-6 Hartree/Bohr² (0.8% of the largest curvature, 2.0e-4). That figure is set by noise in the energy differences, not by the Hessian construction. A gradient without the orbital-relaxation term misses by 3.2e-5. Dispersion leaves the Hessian's asymmetry (7.6e-6 Hartree/Bohr² for PBE/6-31G water) and the projected translation/rotation modes (below 1e-4 cm⁻¹) where they are without it.

On open shells (PBE/STO-3G, bent NH2 doublet on UKS, HCO doublet on ROKS), the D3(BJ) part agrees with 4-point second differences of the D3(BJ) energy to 3.7e-9 and 6.5e-9 Hartree/Bohr² and the frequencies to 9.4e-6 and 3.0e-5 cm⁻¹; the D3 Hessian obeys the translational sum rule to 1e-11 and the rotational one (with the D3 gradient) to 3e-9. The MBD@rsSCS part agrees along three fixed directions with 5-point second differences of the full SCF + MBD energy to ≤ 4.8e-8 Hartree/Bohr² for UKS NH2 and CH2 (triplet) and ROKS HCO and CH2, against curvatures of 6e-5–2e-4; without the orbital-relaxation term the misses are 8.3e-7–1.6e-5. A correction that does nothing reproduces the plain UKS or ROKS finite-difference Hessian bit for bit.

The shifts are small for a single molecule. For PBE/6-31G water at its uncorrected PBE minimum (1594.35, 3500.27 and 3669.29 cm⁻¹), D3(BJ) shifts the three modes by +0.006, −0.077 and −0.095 cm⁻¹, and MBD@rsSCS by +0.107, −0.406 and −0.516 cm⁻¹.

Implicit solvation

Two independent implementations. Both are threaded uniformly through RHF, UHF, ROHF and the KS variants, and both leave the energy bit-identical to vacuum when unset.

ModelWhat it isHow to run itAnchor
IEF-PCM (ferric-pcm)Integral-equation PCM; modified Bondi radii (H 1.10 Å), atom-centred Lebedev spheresCLI [pcm] solvent = "water" (or epsilon), energy runs of rhf, uhf, rohf, ksdft and pdep-rpa (examples/water-pcm.toml), and task = "optimize" for rhf, uhf, rohf and ksdft. Python run_rhf(..., solvent=78.4) or a solvent name; also run_pdep_rpa.water / STO-3G, ε = 78.4: −3.813 vs PySCF IEF-PCM −3.8228 kcal/mol
COSMO (ferric_scf::cosmo)Conductor-like screeningCLI [cosmo] epsilon = 78.39water / cc-pVDZ, ε = 78.39: −5.955 vs PySCF COSMO −5.94 kcal/mol (ferric-scf/tests/cosmo_water.rs)

Neither has cavitation, dispersion or repulsion terms. IEF-PCM has an analytic nuclear gradient (ferric_pcm::pcm_gradient: the tessera-charge potential term plus the S/D/area response term, switching-function derivatives included; equations in crates/ferric-pcm/theory.md §8), added to the RHF, UHF, ROHF and KS gradients whenever RhfConfig.pcm is set. It matches PySCF IEF-PCM nuc_grad_method() to 7e-11 Ha/Bohr on PySCF's 302-point cavity and finite differences of the solvated SCF energy to 3e-7 Ha/Bohr. It needs the cavity built from the molecule (an injected PcmConfig::cavity is refused). There is no PCM Hessian: frequencies with [pcm] are refused. COSMO has no gradient term and no Hessian: every analytic-gradient entry (the SCF gradient dispatchers, optimize_geometry*, optimize_qmmm, finite-difference frequencies, and CLI task = "optimize" or "frequencies" with [cosmo]) refuses a COSMO SCF with "COSMO has no analytic gradient; use [pcm] ... or finite differences". COSMO is energy-only; use [pcm] for geometry work. An unknown solvent name is an error, not a silent vacuum.

Gradients, optimization, frequencies, transition states

Gradients. Analytic nuclear gradients for RHF, UHF, ROHF and KS-DFT (including meta-GGA, with grid response), checked against finite differences and, for DFT, against PySCF. RI-MP2 also has an analytic gradient, closed shell only (there is no unrestricted MP2 nuclear gradient).

Optimization. method.task = "optimize" for rhf, uhf, rohf, ksdft, rimp2 and pdep-rpa (examples/h2_opt.toml, examples/h2-lda-opt.toml); on an open-shell molecule only uhf, rohf and ksdft. Python ferric.run_optimize is RHF.

Harmonic frequencies. method.task = "frequencies" for rhf, uhf, rohf and ksdft (examples/water-frequencies.toml, examples/nh2-rohf-frequencies.toml); Python ferric.run_frequencies(mol, basis_name, reference="rhf", xc=...). Mass-weighted, translations and rotations projected out. The Hessian is:

  • Analytic for closed-shell RHF and for UHF of any multiplicity, with exact four-centre J/K or with density-fitted J and/or K (df_j_aux / df_k_aux, Coulomb operator, no external potential on the fitted routes, a well-conditioned fitting metric), no solvent or polarizable embedding, and a basis up to f functions (RHF and UHF also with an ECP, on the fitted routes too, and with exact J/K also with fixed external point or smeared charges, or a uniform field), and for ROHF with exact four-centre J/K or RI-J / RI-K, an ECP allowed, no external potential and a basis up to f functions: one SCF plus a coupled-perturbed HF solve per nuclear coordinate (for UHF, one solve coupling the α and β orbital rotations; for ROHF, one solve over the closed-virtual, open-virtual and closed-open rotations). This needs a libint2 with second derivatives (the build scripts/install-libint.sh installs).
  • Analytic on the moving grid for closed-shell Kohn-Sham (ksdft, or rhf with a functional): LDA and GGA functionals, pure or global hybrid, exact or density-fitted J and K, Becke weight response included, so it is the Hessian of the surface the KS gradient differentiates. One SCF plus a coupled-perturbed KS solve.
  • Central finite differences of the analytic gradient everywhere else (UKS, ROKS, meta-GGA, VV10, range-separated hybrids, KS with an ECP or an external potential, pruned grids, [dft] dispersion, RI J/K with an external potential or an ill-conditioned fitting metric, solvent, polarizable embedding, g functions, ROHF with an external potential): 6N gradient evaluations. The displacement [frequencies] delta (default 5e-3 Bohr) is a real accuracy knob; the printed Hessian asymmetry, zero in exact arithmetic, is the check that it and the SCF thresholds suit the system.

[frequencies] hessian (Python hessian=) selects it: "auto" (default, analytic where it applies, the moving-grid Hessian for closed-shell KS), "analytic" (the Hartree-Fock analytic Hessian; an error where it does not apply, including every KS reference), "analytic-moving-grid" (the closed-shell KS one; an error where it does not apply) or "fd". The output names the one that ran (Hessian = analytic, analytic-moving-grid or finite-difference; Python .hessian_source).

The moving-grid KS Hessian agrees with "fd" up to the finite-difference error: ≤ 5e-7 cm⁻¹ against Richardson-extrapolated FD at a tight SCF (density_conv 1e-11) on water / STO-3G, for one SCF plus a coupled-perturbed KS solve instead of 6N gradients. Against the default "fd" (step 5e-3 Bohr, default SCF), which "auto" ran for KS before, the wavenumbers move by ≤ 0.071 cm⁻¹ (water / STO-3G, LDA, PBE, B3LYP). Under "auto", open-shell KS, meta-GGA, VV10, range-separated hybrids, ECPs, external potentials, pruned grids, [dft] dispersion, COSX, implicit solvent, fractional occupations, MOM, and systems whose response AO table (basis functions × grid points × 4 for a GGA, 8 bytes each) exceeds 2 GiB (on the default 75×110 grid, a GGA above about 8,100 basis functions × atoms, e.g. about 30 atoms at def2-SVP) run finite differences; "analytic-moving-grid" refuses them before the SCF.

The choice is made before the SCF. An error the analytic path raises after the SCF (for ROHF: the stored spin Fock matrices differing from those rebuilt from the final densities by more than 1e-4 Ha, which a loose density_conv can cause; a response solve that does not converge; non-positive orbital curvature; a first-order rotation above 10 rad/Bohr) is returned as an error under "auto" too, never retried with finite differences; the same holds for the moving-grid KS Hessian (a non-converged SCF included).

Transition states and IRC (Python only, closed shell). ferric.run_saddle(mol, basis_name, xc=...) searches for a first-order saddle by P-RFO, using two finite-difference Hessians with Bofill updates between them; it costs 2(6N + 1) + (n_steps + 1) gradient evaluations and refuses to start from a geometry with no negative mode. hessian="analytic" (or "auto", which falls back to finite differences where the analytic Hessian does not apply) swaps in the analytic RHF Hessian (with xc, "auto" takes the moving-grid KS Hessian), which needs no displaced gradients; "analytic" raises for KS xc, point charges / an external field, ECPs or a basis above the libint second-derivative limit, delta= is only valid with "fd", and result.hessian_source says which ran. The default is "fd". result.is_transition_state() requires both convergence and exactly one imaginary frequency. ferric.run_irc(mol, basis_name, result.imaginary_mode) then follows the reaction path both ways to show which minima the saddle connects (measured about 71 gradients per branch on NH3 inversion). Both refuse multiplicity ≠ 1.

Choosing how exchange is built

Four ways to build K, and the choice is a real one, measured on this code. Butane, one thread; TZ is def2-TZVP (184 functions), QZ is def2-QZVP (528).

[scf] settingWhat it isExact?Scope
(default)Schwarz-screened direct four-centre J + Kyesall SCF types
k_builder = "link"LinK: pair-list-screened direct Kyes (== direct to 9e-12 Ha, butane/def2-SVP)RHF, UHF, ROHF
df_j_aux / df_k_auxdensity-fitted J and K (RI-JK)fitting error, grows with size (see Kohn–Sham DFT above)all SCF types
k_builder = "cosx"seminumerical (COSX) K on a grid; with RI-J active this is RIJCOSXgrid-dependent error, see belowRHF/UHF/ROHF and their Kohn–Sham variants, Coulomb operator only; analytic gradient for all six (see below)

Time for one exchange build (seconds, butane, one thread):

BuilderWhat the time coversdef2-TZVPdef2-QZVP
directJ and K together (one integral sweep)not measured400
LinKKbeing re-measuredbeing re-measured
RI-JKK0.050.43
COSXKnot measured90

Time for a full SCF (seconds, butane, one thread):

Builderdef2-TZVPdef2-QZVP
direct98not measured
LinKbeing re-measuredbeing re-measured
RI-JKnot measurednot measured
COSX358not measured

Energy error against exact exchange:

BuilderSystem / basisError
LinKbutane / def2-SVP9e-12 Ha
COSX, default (sgx (35,194) + final pass on sgx (50,302))water / aug-cc-pVDZ−8.1e-9 Ha
COSX, defaultbutane / def2-SVP−3.4e-5 Ha
COSX, defaultbutane / def2-TZVP−4.3e-6 Ha
COSX, flat (50,110)water / cc-pVDZ5e-6 Ha
COSX, flat (50,110)butane / def2-SVP1.7e-4 Ha
COSX, flat (50,110)butane / def2-TZVP1.2e-4 Ha
RI-JK—not measured on these systems

Start with density fitting whenever its three-index tensor fits in memory (n_aux × n_bf² × 8 bytes: 1 GB for butane/QZVP, 7 GB for octane/QZVP; half that when stored as the packed symmetric triangle). When it does not fit, J is built by Split-RI-J, which stores no tensor and on danuglipron PBE/def2-SVP took 81 s for the whole SCF against 46 s with the tensor in memory and about 590 s spilling or recomputing it. A tensor K still needs is spilled to disk or recomputed each iteration, whichever a measurement on the machine says is cheaper ([scf] jk_storage). It is two to three orders of magnitude faster per build than anything else here. Its error is a fitting error, not zero, and it grows with system size.

Need exact exchange? Direct or LinK; both are exact to the screening threshold as K builders (butane/def2-SVP: link == direct to 9e-12 Ha). LinK's cost is being re-measured, so no LinK timing is quoted here. k_builder is honoured by UHF and ROHF as well as RHF: the open-shell solvers build K_α and K_β from one builder instance, refreshing its density-dependent state per spin. Whether LinK is the faster choice for a given system is a separate question from whether it is honoured; see the cost note above, which is being re-measured. link is skipped with a warning, never silently, whenever density-fitted J/K is active; both link and cosx are skipped with a warning when the functional uses no exact exchange or is range-separated (exchange then comes from the SR/LR fitters).

RIJCOSX. k_builder = "cosx" combines with density-fitted Coulomb: when RI-J is active (df_j_aux named, or the Kohn–Sham default), J comes from RI-J and K from COSX, as in ORCA's RIJCOSX and Psi4's DFDIRJ+COSX. COSX then replaces RI-K: the Kohn–Sham RI-K default is not applied, and an explicitly named df_k_aux next to k_builder = "cosx" is an error. df_j_aux = "" keeps exact J with COSX K. The two approximations add: on water/6-31G (HF) the COSX error is 7.140e-6 Ha with exact J and 7.141e-6 Ha with RI-J, and the cross term is second order (2.8–5.8 × the product of the two errors; 7.8e-10 Ha at the (50,110) grid, 2.3e-11 Ha at (75,302)). Gradients take J from the RI-J derivative and K from the COSX derivative (FD agreement 1.7e-9 Ha/Bohr RHF, 3.6e-9 UHF).

COSX is for large basis sets on systems too big for RI-JK. Its cost per grid point barely moves with angular momentum while analytic exchange grows roughly tenfold from SVP to QZVP, so it wins at high angular momentum, not at large system size. Measured on one thread at the flat (50,110) grid:

  • On butane, against exact direct exchange: at def2-TZVP the full COSX SCF is 3.7× slower (358 s vs 98 s); at def2-QZVP the COSX K build takes 90 s, against about 400 s for the direct build's single J+K sweep, at a relative K error of 5.4e-5.
  • On n-alkanes at def2-SVP, against LinK: slower at every size measured, 1.59× (C20), 1.09× (C32) and 1.22× (C48), with no trend toward parity. At def2-TZVP on C20 it is faster (0.67×).

Below quadruple zeta it is the wrong tool unless RI-JK's three-index tensor does not fit in memory.

A density-driven pair screen (on the product of the integral bound and the local half-transformed density) keeps the K error below 2e-6 Ha at the default threshold, and the half-transforms D·X and X·Gᵀ run over per-batch sparse AO lists (cosx_half_transform = "sparse", the default). At def2-SVP the K build grows as N^1.29–N^1.32 between C20 and C48; that is one family of molecules in one basis.

On the flat (50,110) grid COSX's energy error is about 5e-6 Ha on water/cc-pVDZ (4.9e-6 in cosx_k_anchors.rs, 5.1e-6 in the ORCA comparison run), 1.7e-4 Ha on butane/def2-SVP and 1.2e-4 Ha on butane/def2-TZVP (against exact exchange). ORCA 6.1.1's COSX at its own default grid gives 4.9e-6, 3.8e-5 and 1.3e-5 Ha on the same systems and bases with about half as many points: ORCA's grids are pruned around a 194-point valence shell and it evaluates its final energy once on a finer grid. ferric's default does the same (cosx_grid pruning and cosx_final_pass, below). Refining the flat grid converges water to 3.4e-8 Ha, but butane/def2-SVP stays at 3.4e-5 Ha at Lebedev-302 for every radial grid. That residual is angular: an independent COSX (PySCF SGX, 75 radial shells) on the same system goes from 5.5e-5 Ha at 302 points per shell to -8.5e-6 at 434 and 2.7e-6 at 590. ferric's COSX follows the same path at 75 radial shells: -3.4e-5 at 302, -5.6e-6 at 434 and +1.8e-6 at 590, for 1.8x and 2.2x the 302-point wall time. Reaction energies cancel most of the error (0.02 kcal/mol on an isodesmic alkane reaction at the flat (50,110) grid); absolute energies do not. The knobs, all optional:

  • cosx_grid = { radial = 35, angular = 194, prune = "sgx" } is the default; { radial = 50, angular = 110 } is the flat grid. The angular order matters most: on butane/def2-TZVP the error falls from 1.2e-4 Ha at 110 points per shell to 4.0e-6 Ha at 302, while going from 50 to 100 radial shells changes it by 1e-6 or less. Cost grows with the number of points. angular must be one of 6, 14, 26, 50, 110, 194, 302, 434 or 590. prune = "sgx" prunes the grid the way ORCA's GridX and PySCF's SGX do: five radial regions per atom (NWChem boundaries on Bragg radii) with Lebedev orders from one row, picked by the peak angular — at 194 the regions get 26/50/110/194/110 points. ferric's pruned COSX energy equals PySCF SGX on the same grid to 1.8e-12 Ha. A table without prune is flat.
  • cosx_final_pass = true (the default; false turns it off) re-evaluates exchange once on a larger grid at the converged density and reports that energy; the SCF-grid energy is printed and logged next to it. cosx_final_grid picks the grid (default { radial = 50, angular = 302, prune = "sgx" }). The pass is not self-consistent, but it lands within 1.5e-7 Ha of an SCF converged on the final grid (table below). It is energy-only: gradients and geometry tasks run without it.
  • cosx_grid_schedule = false (default). true runs the early SCF iterations on a coarse pruned { radial = 25, angular = 110, prune = "sgx" } grid and switches to cosx_grid once the largest density-matrix change between iterations drops below 1e-3, restarting DIIS at the switch. Convergence is only accepted after a full iteration on cosx_grid, so the converged energy is the cosx_grid energy; the final pass is unchanged. On water/cc-pVDZ the coarse grid has 4062 points against 10866, and the scheduled SCF reaches the unscheduled energy to 1e-13 Ha in 15 iterations (8 coarse, 7 on cosx_grid) against 12. Its wall time has not been measured. RHF/UHF and their Kohn–Sham variants; ROHF refuses it.
  • cosx_overlap_fit = true (default) applies the Izsák–Neese overlap correction. At the default grid it helps; on coarser grids it makes things worse, and its benefit depends strongly on the molecule (large on water, nil to negative on ethane), so the factor quoted in the literature does not carry over.
  • cosx_backend = "md3c1e" (default) is the batched McMurchie–Davidson integral kernel. "cosx-a" is the per-point libint2 path, about three times slower and kept only as the cross-check the kernel is anchored against.
  • cosx_screen_thresh = 1e-7 (default) is the density-driven pair-screening threshold. 0.0 disables screening bit-identically; 1e-6 already fails a 1e-6 Ha K-error bar on butane. Not available with the "cosx-a" backend.

Setting any cosx_* key without k_builder = "cosx", or k_builder = "cosx" together with a named df_k_aux, is an error.

Which COSX grid. Measured against exact exchange (exact J on both sides, RHF, overlap fit on unless noted), one run each, six threads; times are whole SCFs, indicative only. Errors, E − E_exact in Ha:

Systematomsflat (50,110)sgx (35,194)sgx (35,194) + final sgx (50,302), defaultsgx (50,302)sgx (50,194)flat (50,194)sgx (35,194), no fit
water / aug-cc-pVDZ3+6.1e-6+1.9e-6−8.1e-9−8.1e-9+9.8e-7+1.6e-7+1.6e-6
butane / def2-SVP14+1.7e-4+4.6e-5−3.4e-5−3.4e-5+4.1e-5+5.0e-5−2.7e-4
butane / def2-TZVP14−1.2e-4−4.1e-5−4.3e-6−4.3e-6−3.2e-5−1.6e-5−1.2e-4
benzene / def2-SVP12+8.2e-5−4.8e-5+5.6e-6+5.6e-6−4.8e-5−4.0e-5−8.8e-5
sulfamethoxazole / def2-SVP28+2.5e-4−1.9e-5+2.2e-5+2.1e-5−2.3e-5−2.3e-5−1.9e-4
methane / cc-pVDZ5+8.0e-6+9.2e-6+6.5e-7+6.5e-7+1.0e-5+5.1e-6−1.9e-4
ethane / cc-pVDZ8+1.2e-4−4.3e-6−2.6e-6−2.6e-6−4.5e-6+1.7e-6−9.1e-5
propane / cc-pVDZ11+1.2e-4+1.9e-5−1.0e-5−1.0e-5+1.8e-5+1.8e-5−7.0e-5
C3H8 + CH4 → 2 C2H6, kcal/mol+0.074−0.023+0.003+0.003−0.024−0.012+0.052
points per atom55003545–36293545–3629 SCF, 8109–8214 final8109–82144996–506897003545–3629

Whole-SCF wall time relative to exact-K RHF on the same system (COSX is slower than exact exchange at all of these sizes and bases; see above for where it wins):

Systemexact Kflat (50,110)sgx (35,194)sgx (35,194) + finalsgx (50,302)
water / aug-cc-pVDZ0.3 s15.7×10.1×12.8×21.7×
butane / def2-SVP2.6 s17.7×11.8×14.3×25.5×
butane / def2-TZVP19.0 s5.4×3.9×4.4×7.5×
benzene / def2-SVP3.2 s13.2×8.8×10.6×18.9×
sulfamethoxazole / def2-SVP78.7 s7.2×5.9×6.5×14.4×
methane / cc-pVDZ0.2 s27.0×20.5×22.5×40.2×
ethane / cc-pVDZ1.0 s18.7×12.7×15.8×28.7×
propane / cc-pVDZ3.6 s13.8×9.7×11.0×20.8×

What the table shows:

  • The pruned sgx (35,194) grid uses 0.65× the points of flat (50,110) and is more accurate on seven of the eight molecules, by 1.7× (benzene) to 29× (ethane). On methane it is 1.15× worse (9.2e-6 against 8.0e-6 Ha). Geometry tasks (optimize, frequencies) use this grid, since they run without the final pass.
  • The final pass on sgx (50,302) reproduces an SCF converged on that grid to 4e-8 Ha on seven molecules and 1.5e-7 Ha on sulfamethoxazole, at 0.45–0.59× of that SCF's cost. Together with the pruned SCF grid it is more accurate than flat (50,110) on all eight molecules and on the reaction (0.003 against 0.074 kcal/mol), and faster on all eight (0.80–0.91× flat (50,110)'s wall time). This pair is the default for energies; ROHF/ROKS has no final pass and skips it with a note.
  • More radial shells (35 → 50) at a 194 peak buy little; removing the pruning (flat 194) costs 2.7× the points for errors of the same size. The overlap fit helps on every molecule but water (1.8× to 21×; on water it is 1.2× worse).

COSX gradients.

task = "optimize" and task = "frequencies" with k_builder = "cosx" differentiate the COSX energy itself (grid-function, ESP-integral and Becke-weight derivatives). With the default overlap fit the fitted exchange is not variational in the orbitals, so the gradient adds an orbital-response (Z-vector) term. The gradient is exact for RHF, RKS, UHF, UKS, ROHF and ROKS with cosx_overlap_fit = false, and for RHF and UHF with the default fit; measured against finite differences of the COSX energy it agrees to 2e-9–4e-9 Ha/Bohr, on flat and pruned (sgx) grids alike, and with RI-J (RIJCOSX). The open-shell exchange term is differentiated per spin; an exact-exchange gradient paired with the same COSX energy misses those finite differences by 4e-5–2e-4 Ha/Bohr (HO2 and NH2, B3LYP and ROHF, STO-3G). The MBD@rsSCS gradient's orbital-response term on a UKS or ROKS reference uses the same COSX derivative. The gradient differentiates the SCF-grid energy: with cosx_final_pass the reported energy is the final-grid one, so geometry tasks run without the pass and a gradient of a final-pass result is refused (an ORCA-style gradient evaluated on the final grid misses finite differences of the final-grid energy by 4.6e-7–7.3e-7 Ha/Bohr on water/6-31G, so it is not used). Refused for gradient tasks before the SCF runs: fitted COSX with a Kohn–Sham functional (RKS, UKS, ROKS; the fitted energy's response needs the nuclear derivative of the XC Fock matrix) and fitted COSX for ROHF (no restricted-open-shell response).

Convergence

A few things worth knowing before debugging a stubborn SCF:

Check converged. These routines return a result whether or not they converged; a non-converged SCF is a result with converged = false, not an error. Downstream code that ignores the flag will happily consume a half-converged density.

Multiple solutions. For systems like alkane chains, different initial guesses converge to different SCF solutions, each in its own basin. The guess picks the basin.

Density-fitting has a noise floor. DF-JK introduces an error floor that makes energy-based convergence criteria below roughly 1e-9 meaningless; SCF gates on the density RMS change instead.

Near-linear-dependence. Diffuse (aug-) basis sets on close-packed systems can drive the overlap matrix near-singular; the canonical-orthogonalization threshold is tunable via FERRIC_LINDEP_THRESH.

Secondary-basis guess (Q-Chem BASIS2)

[scf] guess_basis = "6-31g" (Python guess_basis="6-31g" on run_rhf, run_uhf, run_rohf, run_dft, run_ksdft) converges the same SCF (method, functional, charge, multiplicity and settings) in the named basis first, projects the result into the target basis, and starts the target SCF from it. guess_projection picks how the projection is done:

  • "fock" (default; Q-Chem FOPPROJECTION): the small-basis spin densities are projected, D = P D_small Pᵀ with P = S_bb⁻¹ S_bs, and the target Fock built at that density is diagonalised. Closed shell, that Fock build is the target SCF's first iteration. Open shell, the guess Fock is h + J − K_σ with no XC term, which is the form ferric's MINAO open-shell guess also uses.
  • "overlap" (Q-Chem OVPROJECTION): the occupied MOs are mapped, C = S_bb⁻¹ S_bs C_small, and re-orthonormalised in the target metric. For ROHF, the doubly occupied block is orthonormalised first and the singly occupied block is made orthogonal to it.

S_bb⁻¹ is the lindep-filtered pseudo-inverse from the canonical orthogonalizer. A secondary basis missing an element, a mismatched ECP core count, or a projected occupied space that is near-singular in the target basis are errors. A secondary basis larger than the target is allowed, with a warning. The guess is refused with symmetry = true and on periodic systems. An explicit starting density or MOs take precedence, as they do over guess = "minao". With guess_basis set, guess = "minao" | "sad" | "hcore" selects the starting point of the secondary SCF only; the target starts from the projection. The dual-basis energy correction (Q-Chem DUAL_BASIS_ENERGY) is not implemented.

A secondary SCF that does not converge never stops the run. The target SCF then starts from its default guess, a warning is printed, and the report says converged = false, why, and used_as_guess = false. Projecting the unconverged last iterate was tried and rejected: on CN UHF/aug-cc-pVDZ with an STO-3G secondary (which oscillates for 200 iterations), the target started from it failed to converge as well, for both projections, while the default guess converges in 22 iterations. The result reports the secondary SCF's iterations, energy and convergence (Python: result.guess_basis). The report is kept when a later stage supplies the final result: a stability descent, a later ladder rung, or the exact stage after a DF pre-stage.

Cost on re-solves. Every solve without an explicit starting point runs the secondary SCF again: each geometry of an optimization and each displaced geometry of a finite-difference Hessian. A ladder's later rungs, the stability descent's re-solves and the cDFT λ loop start from a carried density or MOs, so they do not repeat it (cDFT builds the guess once, before the λ loop).

The guess can select the state. The converged energy is not guaranteed to equal the default-guess energy. Like any guess, it decides which stationary state the SCF falls into, in either direction:

  • NO UKS-PBE/aug-cc-pVDZ: MINAO −129.7833808133, BASIS2 −129.7833798480 Ha. These are two states 0.97 µHa apart, and BASIS2 lands on the higher.
  • He₂ at 0.3 Å RHF/aug-cc-pVTZ: BASIS2 and the core guess give −3.2458864766 Ha, while MINAO gives −3.2388590827 Ha. Here BASIS2 lands 7 mHa lower.

Use check_stability (and stability_descent) when the state matters.

What it buys, measured in ferric (build 352eabab9, density_conv 1e-8; target-SCF iterations, with the secondary SCF's own iterations listed separately; converged energies agree to ≤ 3e-11 Ha in every row; wall times were taken on a loaded machine and are not quoted):

systemMINAOhcoreBASIS2 fockBASIS2 overlapsecondary SCF
NH₂ UHF 6-31G→aDZ1418 (other state, +0.081 Ha)131413
NH₂ ROHF 6-31G→aDZ12–111211
CN UHF 6-31G→aDZ22no convergence in 200151624
CN ROHF 6-31G→aDZ18–131418
O₂ UHF triplet 6-31G→aDZ1214101111
water dimer RHF 6-31G→aDZ1417131313
water dimer RHF 6-31G→aTZ1417141413
water dimer RHF cc-pVDZ→aTZ1417141414

On closed shells the saving over MINAO is 0 to 1 target iterations, so there is no wall-time win. On open-shell radicals it saves up to 7 target iterations (CN), but the secondary SCF costs about as many iterations as it saves, or more; they are cheap iterations in a small basis. The value is a better starting point on hard open-shell cases, not a faster closed-shell SCF. Fock projection beats overlap projection by 0 to 1 iterations.

Screening

  • Schwarz bounds on every 4-centre path, built so they can never underestimate (zero-valued table entries are floored; left unfloored, one such entry was measured to cost 1.5e-4 Ha)
  • LinK (Ochsenfeld, White & Head-Gordon 1998) — exchange via significant-pair and density-pair lists; its scaling is being re-measured
  • QQR (Maurer, Lambrecht & Ochsenfeld 2012) is implemented and validated as a bound but is not used in production: on LinK it screened only 0.009% more quartets than Schwarz at alkane_16 (measured against a LinK pair-list implementation that skipped quartets; not yet repeated on the current lists)
  • COSX shell-pair screening uses a primitive-level Hölder bound that provably never underestimates; an overlap-based bound can underestimate, which silently corrupts K

QM/MM embedding

QM/MM (electrostatic, polarizable and Gaussian-smeared embedding, link atoms, boundary-charge schemes, and a [qmmm] CLI section that reads a PQR) has its own page: QM/MM, including its CLI section.

Determinism

The Fock build's reduction folds partial matrices in a strict ascending group order, independent of thread count and of the memory band width. Results are bit-identical across RAYON_NUM_THREADS, a property pinned by tests.

A tree-fold reduction would be deterministic but would produce different bits, since floating-point addition is not associative, so the sum must run in ascending order.

Cite

DIIS: Pulay 1980. MOM: Gilbert, Besley & Gill 2008. LinK: Ochsenfeld, White & Head-Gordon 1998. COSX: Neese et al. 2009; overlap fit: Izsák & Neese 2011. CSB/CSAM screening: Thompson & Ochsenfeld 2017. DFT grids: Becke 1988, Treutler & Ahlrichs 1995, Lebedev & Laikov 1999. Functionals through libxc (Lehtola et al. 2018): PBE, B3LYP, ωB97X-V, SCAN, r2SCAN, VV10. D3(BJ): Grimme et al. 2010, Grimme, Ehrlich & Goerigk 2011. IEF-PCM: Cancès, Mennucci & Tomasi 1997; COSMO: Klamt & Schüürmann 1993. P-RFO: Banerjee et al. 1985; Bofill 1994. Full entries in References.

DFT derivations

The exchange-correlation pieces of the Kohn–Sham solver, each stated in the code's conventions and proved by a sympy script that exits nonzero on a mismatch. The full equations, function locations and test cross-references are in crates/ferric-dft/theory.md. Each script also checks mutated formulas (a dropped factor of 2, a wrong libxc packing slot) and requires them to fail.

Conventions

Closed shell, total density matrix \( D \), real AOs \( \chi_\mu \):

\[ \rho = \sum_{\mu\nu} D_{\mu\nu}\chi_\mu\chi_\nu,\qquad \sigma = |\nabla\rho|^2,\qquad \tau = \tfrac12 \sum_b \sum_{\mu\nu} D_{\mu\nu},\partial_b\chi_\mu,\partial_b\chi_\nu . \]

Spin-polarized variables use libxc's packing: \( (\rho_\alpha,\rho_\beta) \), \( (\sigma_{\alpha\alpha},\sigma_{\alpha\beta},\sigma_{\beta\beta}) \) with \( \sigma_{\alpha\beta} = \nabla\rho_\alpha\cdot\nabla\rho_\beta \), and \( (\tau_\alpha,\tau_\beta) \). \( v_\sigma = \partial(\rho\epsilon)/\partial\sigma \), not twice it. Meta-GGAs are \( \tau \)-only (no density Laplacian).

What is proved

PieceIdentityScript
Becke partition\( \partial w^A/\partial \mathbf R_C \) as coded, and \( \nabla_{\mathbf r} w^A = -\sum_C \partial w^A/\partial\mathbf R_C \) (used to build the grid-weight response)scripts/dft_becke_weight_grad_sympy.py
Semilocal \( V_{xc} \)\( \partial E_{xc}/\partial D_{\mu\nu} = \sum_g w_g [v_\rho\chi_\mu\chi_\nu + 2v_\sigma\nabla\rho\cdot\nabla(\chi_\mu\chi_\nu) + \tfrac12 v_\tau\nabla\chi_\mu\cdot\nabla\chi_\nu] \); polarized cross term \( v_{\sigma_{\alpha\beta}}\nabla\rho_\beta \) with coefficient 1; equal to \( v_\rho - 2\nabla\cdot(v_\sigma\nabla\rho) \) up to a divergencescripts/dft_vxc_matrix_elements_sympy.py
\( f_{xc} \) kernelsGGA response \( (u^\sigma, \mathbf w^\sigma) \) is the linearization of \( (v_{\rho_\sigma}, 2v_{\sigma_{\sigma\sigma}}\nabla\rho_\sigma + v_{\sigma_{\alpha\beta}}\nabla\rho_{\bar\sigma}) \), with libxc's packed second derivativesscripts/dft_fxc_kernel_sympy.py
VV10per-point \( v_\rho, v_\sigma \) are \( \partial E_{nl}/\partial\rho_i, \partial E_{nl}/\partial\sigma_i \) (self pair included)scripts/dft_vv10_potential_sympy.py
XC nuclear gradient\( \partial\tau/\partial R_{A,\alpha} = -\sum_b\sum_{\mu\in A}\partial^2_{\alpha b}\chi_\mu (D\partial_b\chi)\mu \): the \( \tfrac12 \) in \( \tau \) cancels against the \( \mu\leftrightarrow\nu \) doubling; grid-response terms \( \partial\alpha\nabla\rho \), \( \partial_\alpha\tau \)scripts/dft_mgga_tau_gradient_sympy.py

The numerical checks of the same identities (finite differences of the energy, PySCF references) are listed per section in theory.md.

Known open issue

The VV10 AO matrix \( V_{nl} \) is not exactly \( \partial E_{nl}/\partial D \): on NH₂/6-31G the directional derivative misses by \( 3.6\times10^{-6} \) relative (\( -1.31\times10^{-7} \) absolute), independent of the step size, while ωB97X without VV10 agrees to \( 8.6\times10^{-14} \). The per-point partials are proved exact, so the residual lies elsewhere on the path from \( D \) to \( V_{nl} \); the cause is not identified. The test is a strict xfail (crates/ferric-python/tests/test_xc_blocks.py).

The MP2 family

The largest method family here. Every variant is density-fitted (RI): each needs an orbital basis and a matching RI auxiliary basis ([mp2] auxbasis, or the auxbasis argument in Python). Formal cost is O(N⁵) for the RI-MP2 transformation. Grades per method.kind are on Capabilities and validation. The [mp2] keys are in Input file. The working equations of every variant on this page (spin factors, frozen core, operator and unit conventions), with the code path behind each, are in crates/ferric-mp2/theory.md; the identities it relies on are checked by scripts/mp2_variant_identities.py.

RI-MP2

What it is. Second-order Møller–Plesset theory on an RHF reference (UHF for an open shell, below) with 3-centre/2-centre density fitting. Canonical (non-RI) MP2 is also implemented, for cross-validation, not production.

Open shells. rimp2 and oo-rimp2 accept an open-shell molecule (multiplicity > 1) from the CLI, for task = "energy" only: they solve the same plain UHF that kind = "uhf" runs, then take unrestricted RI-MP2 (UMP2, as PySCF mp.MP2(uhf)) or unrestricted OO-RI-MP2. There is no unrestricted MP2 nuclear gradient, and [mp2] kappa is refused on an open shell. From Python, run_rimp2 does the same (UHF + UMP2 when multiplicity > 1; result.reference says which, and kappa raises); run_oo_rimp2 is closed shell only. Both unrestricted methods are validated on the CH3 doublet at cc-pVDZ: U-RI-MP2 against ORCA RI-MP2 NoRI, U-OO-RI-MP2 against an independent numpy OO-RI-MP2 (see the anchors).

The other MP2-family kinds (mp3, att-rimp2, scs-mp2, scs-mp2-2terfc, laplace-mp2, laplace-sos-mp2, rs-mp2-rpa) and their Python drivers are closed shell only: set multiplicity = 1. mp2-v switches to a UHF reference when multiplicity > 1 (CLI, energy only); Python run_mp2_v is closed shell only.

The reference SCF: exact, RI-JK or RIJCOSX. The auxbasis above fits the correlation step only. The reference SCF is a separate choice, scf_jk on every MP2-family Python driver ([scf] jk in the CLI, task energy): "exact" (four-centre J and K, the default), "rijk" (RI-J + RI-K) or "rijcosx" (RI-J + COSX K). The fitted modes use jk_auxbasis ([scf] jk_aux), a JK-fit basis with default def2-universal-jkfit. It is never the MP2 auxbasis, because an MP2-fit set used for K costs mHa. rs-mp2-rpa is the exception: its default is "rijk". Every result echoes scf_jk and jk_auxbasis. An unknown mode, jk_auxbasis with "exact", or a k_builder the mode would override is an error (so is any k_builder on rs-mp2-rpa's RI-JK default, in the CLI as in Python). A jk_auxbasis whose name lacks "jkfit" draws a warning. oo-rimp2 accepts only "exact": orbital optimisation rebuilds exact four-centre J/K at every orbital step, so a fitted reference would change only the starting orbitals; threading RI-JK through the OO Fock builds is not implemented. In the CLI a fitted reference cannot be combined with [scf] df_increments (an exact-J/K scheme) or an explicit df_guess = true, and it skips the default DF-guess pre-stage. At aug-cc-pVTZ the four-centre SCF is most of an MP2 single point, so a fitted reference is the usual choice for interaction-energy benchmarks. The fitting error is small and largely cancels in a counterpoise-corrected interaction energy. Measured with PySCF DF-MP2 (frozen core, aug-cc-pvXz-ri for MP2) on the S66 water dimer:

BasisJK auxΔE_total(dimer)ΔIE (CP)
aug-cc-pVDZdef2-universal-jkfit+98 µHa+0.0059 kcal/mol
aug-cc-pVDZaug-cc-pvdz-jkfit (not bundled)+58 µHa+0.0001 kcal/mol
aug-cc-pVTZdef2-universal-jkfit+69 µHa+0.0043 kcal/mol
aug-cc-pVTZcc-pvtz-jkfit+78 µHa+0.0026 kcal/mol
aug-cc-pVTZaug-cc-pvtz-jkfit (not bundled)+63 µHa<0.00001 kcal/mol

Run it. method.kind = "rimp2" (examples/water-rimp2.toml, examples/water-rimp2-frozen-core.toml); Python ferric.run_rimp2(mol, bs, aux).

Accuracy. Proven. RI-MP2 is size-extensive to 2e-12 Ha for a well-separated dimer against twice the monomer (ferric-mp2/tests/rimp2_size_extensivity.rs, which asserts 1e-7).

Knobs. frozen_core defaults to 0 (all electrons correlated). Several published parameterizations below assume frozen core, so set it when you use them.

Attenuated MP2

What it is. MP2 with the Coulomb operator in the correlation energy replaced by a short-range one. Two forms are implemented:

  • erfc: \( \mathrm{erfc}(\omega r)/r \), which is \( 1/r \) at short range and decays to zero beyond roughly \( 1/\omega \).
  • terfc: a short-range operator that is Coulombic inside a cutoff radius \( r_0 \) and attenuated outside it, with a controlled curvature (Dutoi & Head-Gordon 2008). It needs precomputed interpolation tables (FERRIC_TERF_TABLE_DIR, generated under terf-tables/); without them the run errors rather than substituting another operator.

Why. MP2's error for non-covalent interactions has two parts that partly cancel in small basis sets: basis-set superposition error, and an error in the long-range part of the correlation energy. Attenuation removes the long-range part, so attenuated MP2 has no long-range dispersion: its asymptotic \( C_6 \) is zero. The method works in the basis and on the systems it was fitted for; MP2-V (below) adds long-range dispersion back through VV10.

Parameterization. The attenuation parameters are fitted, and each fit belongs to one basis. The aug-cc-pVTZ sets that ferric ships as defaults were fitted on S66 without counterpoise and with frozen core (stated in crates/ferric-mp2/src/scs.rs):

VariantParametersBasis of the fitSource
erfcω; ferric's default is 0.420 Å⁻¹, recorded in the code as the dissertation's erfc optimumaug-cc-pVDZ in the 2012 paperGoldey & Head-Gordon 2012
terfcone cutoff \( r_0 \)aug-cc-pVTZGoldey, Dutoi & Head-Gordon 2013
SCS-MP2(2terfc)\( r_0(1) \) = 0.75 Å, \( r_0(2) \) = 1.05 Å, cOS = 1.27, cSS = 4.05aug-cc-pVTZ, no counterpoise, frozen coreGoldey & Head-Gordon 2014
MP2-V\( r_0 \) = 1.00 Å, b = 11.0, C = 0.0089, terfcaug-cc-pVTZ, no counterpoise, frozen coreGoldey, Belzunces & Head-Gordon 2015

Running a fitted variant in another basis, with counterpoise, or with frozen_core = 0 (the default) is extrapolation outside the fit.

Run it.

Variantmethod.kindExamplePython
erfcatt-rimp2examples/water-attmp2.tomlrun_attenuated_rimp2(..., omega=0.420) (Å⁻¹)
terfcatt-rimp2 with [mp2] att_operator = "terfc", att_r0 (Å)examples/water-attmp2-terfc.tomlrun_terfc_rimp2(..., r0=...)
SCS-MP2 (Grimme)scs-mp2examples/water-scs-mp2.tomlrun_scs_mp2(..., c_os=, c_ss=)
SCS-MP2(2terfc)scs-mp2-2terfcexamples/water-scs-mp2-2terfc.tomlrun_scs_mp2_2terfc(...)
MP2-Vmp2-vexamples/water-mp2v.tomlrun_mp2_v(...)

ω is in Å⁻¹ in the CLI and Python and in Bohr⁻¹ in the Rust API. The SCS-MP2 defaults are Grimme's cOS = 1.2, cSS = 0.333.

Accuracy. att-rimp2, scs-mp2 and scs-mp2-2terfc are Proven (the erfc energy is pinned against PySCF/libcint in testdata/reference/h2o_cc-pvdz_attenuated-rimp2-erfc0p420.json). mp2-v's implementation is Proven (narrow): the damped VV10 half, the terfc-attenuated MP2 half and the erfc control each match an independent PySCF/numpy reference. Its parameterization is unbenchmarked: there is no published MP2-V total energy to compare with, only an aug-cc-pVTZ S66 subset run (n=5, H-bonded/mixed, S66x8 geometry, no CP-anchored reference), and ferric evaluates the VV10 term on the converged HF density (the post-HF variant) on its own 50×50 grid rather than SG-1. Read the header of examples/water-mp2v.toml before quoting a number.

Fitting metric. Each attenuated energy fits its integrals in the metric of the same operator, \( (P|\hat w|Q) \), so the fit is variational for that operator. Robust (Dunlap) fitting is required only when the metric operator differs from the integral operator.

The range split is not additive. MP2 is quadratic in the integrals, so the erfc and erf energies at the same ω do not sum to the Coulomb MP2 energy; the difference is the short-range × long-range cross term. RS-MP2 + LR-RPA (below) is built on it.

RS-MP2 + LR-RPA

What it is. Short-range MP2 plus long-range direct RPA: the attenuated-MP2 idea with the missing long-range correlation supplied by response rather than by a fitted dispersion term. Two formulations:

  • delta-lr (default): \( E_{MP2}[\text{Coulomb}] + (E_{dRPA}[\text{erf}] - 2E_{OS}[\text{erf}]) \), one dRPA call.
  • coupled-rings: \( E_{MP2}[\text{Coulomb}] + \Delta dRPA[\text{Coulomb}] - \Delta dRPA[\text{erfc}] \), two dRPA calls; includes the mixed short/long-range ring diagrams.

Run it. method.kind = "rs-mp2-rpa" (examples/water-rs-mp2-rpa.toml, [mp2] formulation); Python run_rs_mp2_rpa(..., omega=0.420, formulation="delta-lr").

Accuracy. Smoke. The ω→0 and ω→∞ limits reduce exactly to MP2 and to MP2 + dRPA; numbers at production ω are not established on new systems.

Orbital-optimized MP2 and MP3

OO-RI-MP2 (oo-rimp2, examples/water-oo-rimp2.toml, run_oo_rimp2): orbitals optimized for the MP2 Lagrangian, with a level-shifted Newton step, orbital DIIS, Cayley rotations and backtracking. Proven (narrow): the energy matches an independent numpy OO-RI-MP2 to 7.5e-13 Ha (H2O, NH3, UHF CH3 at cc-pVDZ), and the closed-shell analytic gradient matches a finite difference of its own energy to 8e-9 Ha/Bohr. ORCA 6.1.1's OO-RI-MP2 stops 3.7e-8 to 7.0e-8 Ha above the same minimum, so it is only a loose cross-check.

MP3 (mp3, examples/water-mp3.toml, run_mp3): spin-orbital third-order Møller–Plesset through the einsum! framework. Proven.

Laplace formulations

RI-Laplace MP2 (laplace-mp2, examples/water-laplace-rimp2.toml, run_laplace_mp2): MP2 through a Laplace transform of the energy denominator, using pseudo-density matrices in the AO basis. The implementation is dense. It is the correctness reference for that formulation; no reduced scaling has been measured.

Laplace SOS-MP2 (laplace-sos-mp2, examples/water-laplace-sos-mp2.toml, run_laplace_sos_mp2): opposite-spin-only MP2 (Jung et al. 2004, cOS = 1.3 by default) with a minimax Laplace quadrature (n_quad must be 3, 5 or 7; anything else is an error). There is no cSS: dropping the same-spin term is what lets the denominator factorize. With cOS = 1.0 it reproduces the opposite-spin component of RI-MP2 to quadrature error, which is the test anchor. Three formulations (sos_formulation):

  • mo (default) and ao are both exact and agree to round-off.
  • ao-sparse restricts each Boys-localized orbital's pseudo-density to an AO domain of radius domain_cutoff_bohr (required, no default). It is the one approximate variant.

What is measured for ao-sparse:

  • Against the exact AO path on n-alkanes (cc-pVDZ, cOS = 1, n_quad = 7, 2026-07-28), chemical accuracy (absolute error below 1.6 mHa) needs a domain radius of 3, 3, 3, 4, 5 and 5 Bohr for C2, C4, C6, C8, C10 and C12. The diameter grows fivefold over that series, so radius/diameter falls from 0.52 to 0.17.
  • In the STO-3G tests, a 12 Bohr domain that is exact for butane (10.5 Bohr across) is also exact for octane (19.9 Bohr across), and a 4 Bohr domain on butane is already within 0.1%.
  • A 71-atom drug molecule (danuglipron, 31.3 Bohr across, STO-3G) is within 0.05% at 4 Bohr.

The test sos_ao_sparse_truncation_radius_is_transferable_across_sizes in crates/ferric-mp2/src/laplace.rs pins the STO-3G butane/octane comparison: at 12 Bohr butane is exact (relative error below 1e-9) and octane is within 1e-6, and octane at 3 Bohr is worse than at 12 Bohr. The 4 Bohr butane figure and the danuglipron run (recorded in the test's doc comment) are measurements, not assertions. The alkane sweep is kept in the project's working notes.

How to read it (provisional): the radius needed grows, but far more slowly than the molecule. That points to a finite decay length rather than strict saturation: no single radius is shown to suffice at every size.

What is not claimed: any speedup. The domains discard contributions but the tensor algebra is still dense, so there are no timings to report.

Exact and local MP2

method.kind = "rimp2" is the method; it is computed exactly unless a [local] section asks for a local approximation (see Exact and local correlation). The local approximation is closed shell and energy only.

Amplitude-threshold local MP2 (examples/water-rimp2-local.toml; [local] scheme = "amplitude-threshold", eps = 1e-4; Python run_rimp2(..., local="amplitude-threshold", eps=1e-4)): the single-threshold local MP2 of Wang, Aldossary, Shi, Liu, Li & Head-Gordon (2023), with localized virtuals and per-pair domain-local RI fits. eps has no default: it is part of the model. eps = 0 reproduces RI-MP2 exactly; a finite eps carries a one-sided truncation error that grows faster than linearly in eps. Every printout and run-log record of a local run states eps and the fraction of amplitudes kept.

The amplitude equations are solved by preconditioned conjugate gradients to a relative residual of 1e-6 by default. The energy is the stationary Hylleraas form, so its solver error is second order in that residual. Using that form with a loose stop comes from the method's authors: Wang et al. (JCTC 19, 7577, 2023; Eq. 10 and Sec. 3.1) stop at an absolute ‖r‖ < 2.5·√eps, and the 2025 follow-up (ChemRxiv preprint of JCTC 21, 10910) at 5·√eps. ferric uses a fixed relative tolerance instead and gates it on a per-run bound, 3‖PJ‖²·relres²/λ_min with λ_min ≥ 2(ε_LUMO − ε_HOMO). The bound holds for the true residual, up to rounding, so it is evaluated on ‖J + At‖ from the energy's own matvec, not on the solver's recursive residual. If the bound at the solver's stop exceeds 1e-10 Ha, the solve continues from where it stopped to a tighter tolerance, never past 1e-11; with no positive orbital gap the bound is undefined and the solve runs at 1e-11 from the start. Running out of iterations before the tolerance being sought, first or tighter, stops the run with an error; cg_max_iter and an explicit cg_rtol are fields of the Rust AmplitudeLmp2Config, not CLI or Python options. The certificate (result.local in Python, "local" in the CLI JSON) records cg_rtol, cg_rtol_source, cg_tightened, cg_relres, cg_true_relres, cg_energy_error_bound, cg_energy_error_target and cg_energy_target_met, and solver_warnings says if a converged solve did not meet the target, because the target needs more than the 1e-11 floor or the true residual drifted from the recursive one. The bound limits solver error only, not the truncation error of eps.

The exact RI-MP2 reference that measures that error is opt-in. It is a full canonical RI-MP2 and forms the global (naux, nocc·nvir) tensor, so a run with it switched on is not reduced-cost. In the CLI, [local] reference = true computes it and prints the local error against it; without it the output reads E_corr(canonical RI) = not computed (opt-in: set [local] reference = true) and the run log's e_corr_canonical_ri is null. In Python, compute_reference=True computes it; by default result.local["e_corr_canonical_ri"] is None.

Integral-direct local MP2 ([local] integral_direct = true, examples/alkane8-rimp2-local-direct.toml; Python integral_direct=True) is the reduced-cost path. Its correlation assembly never forms the global 3-index tensor; only the opt-in reference does. Locality comes from an integral-free pair gate (gate_cal), per-occupied auxiliary-fit and virtual domains (aux_radius, virt_radius), and truncation of each orbital's AO support (ao_tail). With every map at its trivial setting it reproduces the global 3-index path and canonical RI-MP2 (tests/lmp2_direct.rs).

What is measured for the integral-direct local MP2 (n-alkanes C20 → C48, 6-31G with cc-pVDZ-RI, a quiet machine, 2026-09-07; benchmark bench_direct_alkane_series in crates/ferric-mp2/tests/lmp2_direct.rs). The run froze the carbon cores (frozen_core = number of carbons) and calibrated the pair gate (0.7 Coulomb, 0.02 erfc with ω = 1.0) at eps = 1e-4; the defaults are all-electron with no pair gate. Timings are the correlation stage (assembly + solve) and exclude the canonical reference:

  • Correlation-stage cost grows as about N1.24 with the erfc kernel and N1.4 with Coulomb, fitted to the last three sizes.
  • It is at about parity with canonical RI-MP2 at C20 (about 1.1× slower) and about 6× faster at C32 (5.7–6.3×).

How to read it (provisional): sub-quadratic on this series, but the fit has three points on one family of molecules in one basis. It is not shown to be linear, and it is not measured on 3-D or diffuse systems.

Schwarz triple cut (schwarz_skip, default 1e-5) applies to the Coulomb, erfc and terfc kernels alike. A shell triple is skipped when sqrt(max (P|P)) * sqrt(max (ab|ab)) < schwarz_skip, both factors from the run's own operator; schwarz_skip = 0 evaluates every triple. For terfc the factors come from the table engine's own diagonals and carry a 1e-5 relative inflation for table error, and the cut is only available inside the checked box: orbital shells up to g, r0 between 0.3 and 2.5 Å and r0·ω ≤ 8.9 (outside it the run errors and names schwarz_skip = 0.0). Measured for terfc (n-alkanes, 6-31G with cc-pVDZ-RI, eps = 5e-5, default maps, plain Schwarz, 2026-10-11, a loaded machine so ratios only, order alternated, two repeats):

Systemoperatorwall vs schwarz_skip = 0 at 1e-6at 1e-5energy change at 1e-5
C12linked (r0 = 1.0 Å, r0·ω = 0.707)2.3–2.5× faster2.7×1.5e-7 relative
C12NCP (r0 = 0.991 Å, ω = 1.3663 Å⁻¹)2.4–2.5×2.6–2.9×3.6e-8
C16linked3.3–3.5×3.5–3.8×1.7e-7
C16NCP3.1–3.9×3.3–4.1×3.6e-8

At 1e-5 the cut skips 55% of the shell triples at C12 and 67% at C16 (51% and 63% at 1e-6). The pair gate (gate_cal = 0.7) leaves the C12 result unchanged. At 1e-6 the energy changes by 1e-10 (C12 linked) to 1.8e-9 (C16 NCP).

Local MP2 without integral_direct makes no scaling claim. Its assembly is pair-local (no dense J is formed), but it still builds the global 3-index tensor. Use it as the reference implementation and for small systems.

Cite

RI-MP2 auxiliary sets: Weigend et al. 1998. SCS-MP2: Grimme 2003. SOS-MP2: Jung et al. 2004. OO-MP2: Lochan & Head-Gordon 2007; Bozkaya et al. 2011. Laplace MP2: Häser & Almlöf 1992. Attenuated MP2: Goldey & Head-Gordon 2012; terfc: Dutoi & Head-Gordon 2008, Goldey, Dutoi & Head-Gordon 2013; SCS-MP2(2terfc): Goldey & Head-Gordon 2014; MP2-V: Goldey, Belzunces & Head-Gordon 2015. LMP2: Wang et al. 2023. Robust fitting: Dunlap 2000. Full entries in References.

Coupled cluster

"RI-CC" here means the two-electron integrals in the amplitude equations come from density fitting (three-centre B tensors from an RI auxiliary basis), not from exact four-centre integrals. Everything below needs an orbital basis and an RI auxiliary basis. Formal cost is O(N⁶) for CCSD and O(N⁷) for (T).

CCSD and CCSD(T)

What it is. RI-CCSD and the perturbative triples correction (T), in two implementations:

  • Spin-adapted, closed shell (ccsd_closed_shell, ccsd_t_closed_shell): amplitudes over spatial orbitals, the algorithm PySCF's cc.CCSD uses. This is what both the CLI and Python run for an RHF reference. Its VVVV block is 16× smaller than the spin-orbital one; measured about 8–10× faster than the spin-orbital CCSD at cc-pVDZ, and the (T) step 9.6–42× faster.
  • Spin-orbital (ccsd, ccsd_t): built from the same RHF spatial orbitals and kept as the cross-check of the spin-adapted solvers, so it is closed shell only as well. Its (T) streams one occupied triple at a time, so memory is O(no·nv³)-class rather than the dense six-index tensor.

No open-shell CCD, CCSD or CCSD(T) exists in ferric: every CC solver reads restricted orbitals and refuses an unrestricted reference. The only open-shell coupled-cluster-type method is LinLCCD(hh): the CLI linlccd kind is closed shell, and its open-shell (UHF) version is library-only (ferric_cc::linlccd_u::u_linlccd).

Run it.

  • CCSD: method.kind = "ccsd" (examples/water-ccsd.toml, water/cc-pVDZ); Python ferric.run_ccsd(mol, bs, aux).
  • CCSD(T): method.kind = "ccsd(t)" (examples/water-ccsd-t.toml); Python ferric.run_ccsd_t(mol, bs, aux).
  • CCD: method.kind = "ccd" (examples/water-ccd.toml); Python ferric.run_ccd(mol, bs, aux).
  • All three kinds are closed shell only and refuse multiplicity > 1.

Aux basis. The RI error is not negligible at CC accuracy: on water / cc-pVDZ with cc-pvdz-ri, RI-CCSD differs from exact-integral CCSD by about 1.3e-4 Ha. Choose the aux basis for the accuracy you need.

Accuracy. ccsd is Proven.

QuantitySystem / basisReferenceAgreementPinned by
CCSD correlation energyH2 / STO-3Gexact-integral numpy−0.02052453 Haferric-cc/src/ccsd.rs::test_ccsd_h2_sto3g
Closed-shell (T)H2O / cc-pVDZPySCF ccsd_t()~1e-6 Ha (test asserts 1e-4)ferric-cc/src/ccsd_t_closed_shell.rs::closed_shell_t_h2o_ccpvdz_matches_pyscf
Streaming vs dense spin-orbital (T)H2O / cc-pVDZferric's former dense path5e-16 Haferric-cc/src/ccsd_t.rs::streaming_matches_dense_h2o_ccpvdz

Limits. Closed-shell entry points only in the CLI and Python. The spin-adapted (T) rejects spin-orbital amplitudes with a typed error rather than mixing conventions.

Particle-particle ladder: dense or RI

The particle–particle ladder Σcd Wabcd τijcd of closed-shell CCSD can be evaluated two ways, chosen by [cc] ladder (Python run_ccsd(ladder=) / run_ccsd_t(ladder=)):

  • dense holds the chemist (ab|cd) block in core and builds Wabcd every iteration: nv⁴ memory, a few times over.
  • ri holds neither the (ab|cd) block nor Wabcd. It rebuilds the (ac|bd) entries from the RI 3-index tensors every iteration in ablk·nv³ panels: at most one nv⁴-sized panel at full width (dense holds four nv⁴ buffers at its peak), and narrower panels when the budget requires. It is the same algebra over the same tensors, so it is exact: on water/cc-pVDZ |ΔE| and max|Δt| are ≲ 3e-17 (round-off), and the alkane energies below agree to all 10 printed digits.
  • auto (the default) runs dense whenever its memory plan fits the budget and ri otherwise, so it is never refused where ri would run. When it falls back to ri it says so on stderr. The route taken is printed (ladder = dense (auto)), logged, and returned (CcResult::ladder_route; Python CcResult.ladder_route).

Reproducibility. Under auto, a closed-shell CCSD job whose dense plan exceeds the memory budget runs on the RI ladder instead of being refused. Because auto decides against the budget, a run without an explicit [memory] budget_gb (auto-detected as 0.8 × the RAM available at start-up) can take different routes on the same input from one run to the next. The energies agree to round-off either way; the time does not. Set ladder explicitly, or fix the budget, for reproducible timings.

Measured 2026-10-26, alkane/cc-pVDZ (cc-pvdz-ri), OPENBLAS_NUM_THREADS=1, RAYON_NUM_THREADS=4, routes interleaved one per process, load average 3–6 from other jobs (benchmarks/harness/examples/ri_ccsd_memory.rs). RI ran at full block width (ablk = nv, the default-budget choice). Per-iteration time is (time for k iterations − setup time) / k, repeated 4× per route for water, 3× for C3H8 and 2× for C4H10/C5H12; ratios are of the medians. Peak RSS is one converged run per route.

SystemnvPer iteration, densePer iteration, RIRI / densePeak RSS, densePeak RSS, RI
water197.8 ms8.3 ms1.06——
C3H8691.27 s1.64 s1.301.50 GB0.70 GB
C4H10893.2–3.7 s5.2–5.3 s1.503.79 GB1.67 GB
C5H121098.8–10.8 s14.5–14.6 s1.487.90 GB3.40 GB

RI is 1.3–1.5× slower per iteration from nv ≈ 70 up and uses 2.1–2.3× less memory, which is why auto takes it only when dense does not fit. The RI route still holds the no·nv³ (ia|bc) block in core, so it lowers the nv⁴ ceiling but does not remove every large tensor.

LinLCCD and ωB97X-L-V

LinLCCD(hh) is linearized coupled-cluster doubles with the hole–hole ladder kept to all orders, closed shell only. The ladder keeps the correlation energy finite as the HOMO–LUMO gap closes, where MP2 diverges. method.kind = "linlccd" (examples/water-linlccd.toml). Proven (narrow): no other quantum chemistry code implements LinLCCD(hh), so its energy is checked against an independent numpy solve of the same equations on PySCF density-fitted integrals (H2O and NH3 through the closed-shell path, and UHF OH through the library-only open-shell path; agreement ≤1.2e-12 Ha). With the ladder off it reduces exactly to RI-MP2, and with exact integrals its driver terms reproduce canonical MP2.

[mp2] linlccd_variant (Python run_linlccd(variant=...)) selects the method's ladder terms: hh (default), drivers-only (no ladder, equal to RI-MP2) or full (hole–hole plus particle–particle, with CCD-like VVVV memory). Every variant is computed exactly by default. All three variants match an independent numpy solve on the same density-fitted integrals (H2O and NH3, 6-31G and cc-pVDZ, ≤ 4.4e-13 Ha); drivers-only also matches PySCF DFMP2.

Local LinLCCD. With [local] scheme = "amplitude-threshold" and eps (examples/water-linlccd-local.toml; Python run_linlccd(..., local="amplitude-threshold", eps=1e-4)) the same variant is solved in the Boys-localized basis with pair amplitudes at or below eps dropped (see Exact and local correlation). eps has no default and is printed and logged with the kept fraction; eps = 0 reproduces the exact LinLCCD of the same variant and the numpy reference (≤ 4.4e-13 Ha, every variant), which is how the local path is anchored. [local] reference = true also runs the exact LinLCCD and prints the local error. Closed shell and energy only.

ωB97X-L-V is a double-hybrid functional that uses short-range LinLCCD(hh) instead of MP2 for its correlation term. It converges its own ωB97X-L Kohn–Sham reference (a non-converged reference is an error), then adds the LinLCCD(hh) correction on those orbitals. method.kind = "wb97x-l-v" (examples/water-wb97xlv.toml). [dft] lambda and omega override the published 0.6 and 0.1 Bohr⁻¹; omitting them gives the published values. λ enters the amplitude equations as well as the energy (the paper's eqn 22), so the correlation term is quadratic in λ at leading order. Validated against PySCF with the published parameters plus a numpy LinLCCD(hh) on water and OH, and against the paper's Be₂ bond energy (2.3 kcal/mol; ferric 2.299).

MP2-based double hybrids

B2PLYP and DSD-PBEP86: a KS reference with weighted exchange and correlation components, plus scaled (SCS-)RI-MP2 correlation. method.kind = "b2plyp" / "dsd-pbep86" (examples/water-b2plyp.toml); Python ferric.run_double_hybrid(mol, bs, aux, kind="b2plyp"). Spike: no comparison to a reference code yet.

Implementation

The equations each solver implements, with file and line, are in crates/ferric-cc/theory.md.

All contractions go through einsum!, a macro that maps tensor contractions onto BLAS3 GEMMs. The permutation copies that feed those GEMMs are parallelized, because for a strided permutation the copy can dominate the contraction it feeds: measured at 47% at nv = 40 and 70% at nv = 80. The copies are bit-identical regardless of thread count, since a permutation writes each output element once; a test pins this.

Memory

The amplitude tensors dominate and grow as \( n_o^2 n_v^2 \), or \( (2n_o)^2 (2n_v)^2 \) in the spin-orbital drivers. Memory budgets are enforced: an oversized job is refused with a breakdown naming the dominant term instead of being OOM-killed partway through.

Cite

CCSD: Scuseria, Janssen & Schaefer 1988; spin-adapted closed-shell CCSD equations: Hirata et al. 2004. (T): Raghavachari et al. 1989; closed-shell (T) algorithm: Rendell, Lee & Komornicki 1991. LinLCCD(hh): Carter-Fenk 2025. ωB97X-L-V: Ransford & Carter-Fenk 2026. B2PLYP: Grimme 2006. DSD-PBEP86: Kozuch & Martin 2011. Review: Bartlett & Musiał 2007. Full entries in References.

CAS-CI

ferric runs complete-active-space configuration interaction (CAS-CI) from a closed-shell RHF reference. It is a Rust library routine only: the CLI and the Python bindings do not expose it.

The Hamiltonian, the determinant phase convention and the Slater-Condon rules are derived in crates/ferric-ci/theory.md. The phase conventions are checked by scripts/casci_slater_condon_phases_sympy.py.

Scope

Implemented
Spacefull CI inside n_active spatial orbitals; no RAS1/RAS3 restrictions
Referencerestricted (closed-shell) RHF, a singlet with n_alpha == n_beta in the active space
Rootsthe single lowest root
SolverCI-native Davidson, diagonal preconditioner 1/(H_ii - λ)
Entry pointsRust only: ferric_ci::run_cas_ci (re-exported as ferric::ci)
Not implementedopen-shell or non-singlet references, S² control, multiple roots, CLI method.kind, Python function

With no inactive orbitals the method is FCI in the basis. A call that fails the bookkeeping check, 2 * active_start + n_alpha + n_beta == nelec, returns an error. So does a non-restricted reference, n_active == 0, or n_active > 63 (the u64 string limit).

Energy

$$ E_\text{CASCI} = E_\text{core} + \lambda_\text{min}(H_\text{act}), $$

where $E_\text{core}$ is the nuclear repulsion plus the energy of the active_start doubly occupied inactive orbitals, and $H_\text{act}$ carries the inactive-core Fock potential in its one-electron part. e_active is e_total - e_core, the quantity PySCF reports as CASCI.e_cas.

Library use

use ferric_ci::{run_cas_ci, CasCiConfig};

// `mol`, `prep` and `rhf` come from a converged restricted solve_rhf
// with exact J/K (no density fitting), as in the validation tests.
let cfg = CasCiConfig {
    n_active: 6,
    n_elec_active: (3, 3),
    active_start: 4,          // MOs 0..4 are doubly occupied and inactive
    ..Default::default()
};
let res = run_cas_ci(&mol, &prep, &rhf, cfg)?;
println!("{res}");            // energy, determinant count, iterations

CasCiResult holds e_total, e_active, e_core, n_determinants, ci_vector (index ia * n_beta + ib), converged and iterations. Defaults: conv_thresh = 1e-9, max_subspace = 50, max_iter = 200.

Limits

  • The active-space integrals come from a dense AO ERI tensor, O(nbasis⁴) memory. Use it on minimal and double-zeta basis sets.
  • The sigma build is an O(N_det²) loop over determinant pairs. It does not use the string-based Knowles-Handy/Olsen algorithm.
  • The Davidson subspace needs 2 * max_subspace * N_det * 8 bytes. memory_budget_bytes bounds the integral transform and the subspace; None resolves through FERRIC_MEM_BUDGET_GB, then 0.8 of available RAM, then a 2 GiB floor.

Validation

Compared with PySCF mcscf.CASCI on an exact-integral RHF with the same orbital window, and with a dense diagonalization of the full active-space Hamiltonian:

SystemActive spaceBasisLargest difference
N2, r = 1.10 and 2.20 ÅCAS(6,6)cc-pVDZE_CASCI 1.4e-11 Ha
H2OCAS(4,4)6-31Ge_core and e_active 2.6e-10 Ha

Each system is also run with the window moved down one orbital. The test tolerance is 3e-9 Ha. The stretched N2 reference is the symmetric RHF, which is internally unstable toward a symmetry-broken RHF 0.19 Ha lower; both codes use the symmetric state.

The sign convention is checked three ways, because energy comparisons cannot detect a diagonal $\pm1$ similarity of $H$:

  1. tests/slater_condon_brute_force.rs compares general_element, sigma and the diagonal with an independent bit-loop second-quantized $H$ on random asymmetric integrals, CAS(4,4) to CAS(6,6), odd and even $N_\alpha$. The largest difference is 8.9e-15.
  2. scripts/casci_slater_condon_phases_sympy.py builds the Jordan-Wigner operators symbolically, verifies the anticommutation relations, and shows that a line-for-line port of general_element equals the explicit $H$ as an exact linear form in $h_{pq}$ and $(pq|rs)$ for four $(n, N_\alpha, N_\beta)$ sectors. Four sign mutations and the interleaved orbital order each fail.
  3. The PySCF energies above fix the spectrum.

The grade is on Capabilities and validation. Sources: crates/ferric-ci/tests/validation_casci.rs and scripts/validation/gen_casci.py.

RPA, GW and excited states

Methods built on the density–density response function: RPA correlation energies, GW quasiparticle energies, BSE and TDDFT excitations, and polarizabilities and \( C_6 \) coefficients. All need an RI auxiliary basis ([rpa] auxbasis). The [rpa], [gw] and [tddft] keys are in Input file; grades are on Capabilities and validation.

What PDEP does in ferric

The independent-particle response \( \chi_0(i\omega) \) is built in the RI auxiliary basis from the three-centre B tensors, as an explicit (Adler–Wiser) sum over every occupied–virtual pair \( ia \), weighted by \( 4\varepsilon_{ia}/(\omega^2 + \varepsilon_{ia}^2) \) (sternheimer::dielectric_matrix). The sum over empty states is still there.

PDEP (projective dielectric eigenpotentials) then works in the eigenbasis of the static dielectric matrix in that RI space. Eigenpotentials whose eigenvalue is within trunc_thresh of 1 (default 1e-4) carry almost no screening and are dropped, so the frequency-dependent work runs in a smaller basis. That compression, not the removal of the empty-state sum, is what PDEP contributes here. How much it saves depends on the threshold; runs that need the full-rank answer set trunc_thresh = 0.0, as the GW, BSE and \( C_6 \) examples do.

RPA correlation energy

What it is. Direct RPA (dRPA) correlation from the dielectric eigenvalues on an imaginary-frequency quadrature.

  • PDEP-RPA, closed shell: method.kind = "pdep-rpa" (examples/water-pdep-rpa.toml); Python ferric.run_pdep_rpa. Proven.
  • U-PDEP-RPA, open shell over a spin-summed dielectric. From the CLI, set method.kind = "pdep-rpa" with multiplicity > 1 and task = "energy": the CLI solves UHF (UKS with [rpa] xc) with MOM after 5 iterations and runs U-PDEP-RPA on it. It is CLI-only: Python run_pdep_rpa is closed shell only. The library (ferric_rpa::run_u_pdep_rpa) also accepts a ROHF (or ROKS) reference, which it semi-canonicalizes first: each spin uses the orbitals and energies of its own Fock matrix, diagonalized in its occupied and virtual blocks (see the anchors). It runs the Lanczos (default) and Davidson eigensolvers, PDEP truncation (trunc_thresh), the Dense and Laplace χ₀ backends and run_diagnostics (full-basis U-RI-dRPA check). It refuses chi0_sparsity = "boys", and "auto" when that resolves to Boys for the molecule: the Boys-screened χ₀ is closed shell only.
  • Attenuated RPA: short-range correlation with an erfc operator.
  • RS-MP2 + LR-RPA: short-range MP2 plus long-range dRPA, on the MP2 page.

The static eigensolve defaults to Lanczos, with a dense path for small problems. Geometry optimization with pdep-rpa is supported (task = "optimize") on a closed-shell RHF reference.

Exact and local dRPA

What it is. method.kind = "drpa" (Python ferric.run_drpa) is dRPA@HF computed by the drCCD Riccati equations in the Boys-localized basis, closed shell, energy only. It is exact by default: no amplitude is truncated (examples/water-drpa.toml), and the energy equals the canonical plasmon formula to ≤ 1e-12 Ha. Riccati, plasmon and full-rank PDEP-RPA are algorithms for the same exact dRPA; measured agreement is ≤ 2.6e-14 Ha (H2/STO-3G, water/6-31G with and without frozen core, PDEP with trunc_thresh = 0 at 64 Gauss–Legendre points). Proven (narrow).

Which exact algorithm. The Riccati solve holds a ring-product plan of no³·nv² numbers, no times the size of the amplitudes, so it is the small-system path: C12 thrashed and was then killed for memory. A run that cannot fit the memory budget is refused before the SCF and pointed at pdep-rpa with [rpa] trunc_thresh = 0, which gives the same energy (to its frequency-quadrature error) at far lower memory and is faster at every size measured (n-alkanes C4–C16).

The local approximation ([local] scheme = "amplitude-threshold" with eps, examples/water-drpa-local.toml; Python run_drpa(..., local="amplitude-threshold", eps=1e-4)) drops pair amplitudes whose localized |2(ia|jb)| is at or below eps. eps has no default and is printed and logged with the kept fraction; eps = 0 is the exact method. dRPA is not variational, so the error is first order in what is dropped. It is positive (less correlation) at every point measured, which is a measurement, not a guarantee, and grows faster than linearly in eps: on n-octane / 6-31G it is 6.7e-7, 3.9e-5, 5.5e-4 and 1.0e-2 Ha at eps = 1e-6, 1e-5, 1e-4 and 1e-3, keeping 82%, 50%, 18% and 3% of the amplitudes. [local] reference = true (Python compute_reference=True) also computes the canonical plasmon dRPA and prints the error against it. [local] eps_sweep (Python run_drpa_scan) evaluates several eps on one SCF and one localized assembly. Proven (narrow) through its exact limit.

What is measured (n-alkanes, 6-31G / cc-pVDZ-RI, frozen carbon cores, Coulomb, single thread; local error against the plasmon formula, PDEP error against full-rank PDEP at 32 quadrature points). The fraction of the truncated object retained at 1 kcal/mol error:

SizeLocal dRPA (amplitudes kept)PDEP (modes kept)
C423.3%17.9%
C810.0%21.5%
C124.9%22.6%
C162.8%23.3%

The amplitude threshold compresses more as the molecule grows, and PDEP's fraction stays flat. The two fractions are of different objects (no²nv² amplitudes against naux modes), so this is not a cost comparison. In wall time at a matched error of about 1 kcal/mol (reference off), truncated PDEP stays about 8× faster from C4 to C16 (C16: 165.7 s local at eps = 1e-4 against about 21 s PDEP), and both grow at the same rate in that range. No speedup over PDEP is claimed for local dRPA.

GW

The equations and conventions of this section, the BSE-TDA and the RPAx polarizability, with the code paths and tests behind each, are in crates/ferric-gw/theory.md; the identities it relies on are checked by scripts/gw_self_energy_sympy.py.

What it is. Quasiparticle energies from the GW self-energy: G0W0, COHSEX, evGW0 and evGW, closed shell, plus unrestricted U-GW. The starting point is HF by default or a KS functional ([rpa] xc). From a KS starting point the static term Σx − v_xc enters the G0W0, evGW₀ and evGW quasiparticle equation (for U-GW, each spin's own equation with that spin's v_xc), so Σc is evaluated at the shifted root; COHSEX and U-COHSEX, which are static, add it to the quasiparticle energy.

Run it. method.kind = "gw" with [gw] method = "g0w0" (examples/water-g0w0-pbe.toml, open shell examples/oh-ugw.toml); Python ferric.run_gw, run_u_gw. The open-shell reference is UHF by default; [gw] reference = "rohf" (Python run_u_gw(reference="rohf")) uses ROHF instead, or ROKS with [rpa] xc (examples/oh-ugw-rohf.toml), semi-canonicalized per spin as for U-PDEP-RPA.

Accuracy. Smoke; treat results as about ±0.3 eV.

QuantitySystem / basisReferencePinned by
G0W0@HF, G0W0@PBE, U-G0W0@UHF, ECP, COHSEX, evGW₀, evGW quasiparticle energies (HOMO−2 to LUMO+2)H2O, NH3, N2, OH, CH3, NH2, O2, CH2, I2, Xe, Ag2 / cc-pVDZ, aug-cc-pVDZ(-PP)PySCF gw_ac/ugw_ac at matched settings ([rpa] n_quad = 100, trunc_thresh = 0); see What is validatedferric-gw/tests/validation_gw.rs
G0W0@PBE HOMO IPH2O / cc-pVDZPySCF gw_ac, 11.1714 eV; asserted to <0.1 eVferric-gw/tests/g0w0_pbe_h2o.rs
U-G0W0@UKS/PBE quasiparticle energies, Σx − v_xc inside each spin's equationOH, CH3, NH2 / cc-pVDZPySCF ugw_ac Σc(ef + iω), quasiparticle equation solved in numpy: ≤2.6e-8 Ha on orbitals whose quasiparticle equation has a single rootferric-gw/tests/validation_gw.rs
Σx − v_xc placement in U-GW (inside the quasiparticle equation; none for a UHF reference)OH / STO-3Ginternal: shifted residual, bit-identity of the no-shift pathferric-gw/tests/u_gw_ks_shift.rs
U-G0W0@UHF α-HOMO IPOH / cc-pVDZ~13–14 eV window, brackets experiment 13.02 eVferric-gw/tests/oh_u_g0w0.rs

Limits. The quasiparticle equation is solved by a Newton root search on the self-energy, which is fragile near \( \Sigma_c \) poles. Runs report whether each root and each eigenvalue-self-consistency loop converged, and warn when one did not; check those flags.

BSE-TDA

What it is. Bethe–Salpeter excitation energies in the Tamm–Dancoff approximation on top of G0W0@HF quasiparticle energies, closed shell.

Run it. method.kind = "bse-tda" (examples/water-bse-tda.toml, and a set of *-bse-tda-augdz.toml examples for small organics); Python ferric.run_bse_tda.

Accuracy. Smoke. Given the same quasiparticle energies, the lowest five singlet excitation energies match an independent numpy BSE-TDA (PySCF density-fitted integrals, static RPA W) to 1.8e-10 Ha and their oscillator strengths to 2.6e-9, for H2O at cc-pVDZ and aug-cc-pVDZ and NH3 and CH2O at cc-pVDZ. The quasiparticle energies come from the internal G0W0@HF, which matches PySCF only at [rpa] n_quad = 100 and trunc_thresh = 0; the defaults are coarser. The core and high-virtual quasiparticle energies are ill-conditioned, which moves the lowest five excitations by at most 2.2e-7 Ha.

TDDFT and TDA

What it is. Linear-response excitations in the Tamm–Dancoff approximation (TDA, which is CIS for an HF reference) and the full Casida equations, closed shell.

Run it. method.kind = "tda" or "tddft" with [tddft] n_roots and optionally xc (examples/water-tda.toml, examples/water-tddft-pbe.toml); Python ferric.run_tddft(mol, bs, aux, functional=..., method="tda") or method="casida".

Scope. Closed-shell references, singlet excitations. With a DFT reference the \( (ia|f_{xc}|jb) \) XC-kernel term is included; with no functional the result is CIS/TDHF. τ-only meta-GGAs (SCAN, r2SCAN, TPSS) take the τ kernel; VV10 and range-separated functionals are refused. Grade: Proven (narrow, closed shell): water, formaldehyde and NH3 at 6-31G and aug-cc-pVDZ with HF, LDA, PBE and B3LYP match PySCF TDA/TDDFT to at most 8.1e-4 eV on the default pruned grid (2.8e-6 eV for B3LYP/aug-cc-pVDZ on the reference's flat grid), test bar 1e-3 eV, and water/6-31G r2SCAN matches on the flat 75 × 110 grid (ferric-tddft/tests/validation_tddft.rs).

Meta-GGA response needs a finer grid. A TDA/TDDFT run on a meta-GGA (SCAN, r2SCAN, TPSS) with no [dft] grid key (Python: no grid_* kwarg) runs its reference SCF and its f_xc kernel on 150 × 434 with the default nwchem-radial pruning and 1e-14 weight cut. Every other functional keeps the production 75 × 110 grid. Any grid key replaces the meta-GGA default. The run prints the grid it used (CLI: the TDDFT grid line; Python: TddftResult.grid).

On the same grid, ferric and PySCF agree: fed ferric's own coordinates and weights, PySCF reproduces ferric's r2SCAN excitation energies to 6e-8 eV and its SCF energy to 1e-12 Ha. The grid itself is the error. Largest deviation of the five lowest TDA roots from a flat 150 × 590 grid (Casida roots follow the same pattern):

Grid (nwchem-radial unless noted)Points (water)r2SCAN water/6-31Gr2SCAN water/aug-cc-pVDZr2SCAN H2CO/6-31Gr2SCAN H2S/6-31GSCAN water/6-31G
75 × 110 (production)13 9142.1e-31.0e-33.9e-35.5e-34.2e-2
99 × 30243 0443.9e-42.0e-45.0e-43.9e-41.7e-2
99 × 302, nwchem (angular only)58 7203.5e-41.5e-43.5e-43.8e-41.2e-2
99 × 43460 1028.4e-42.1e-43.2e-44.0e-43.5e-2
150 × 30265 3261.6e-42.2e-52.8e-49.8e-54.2e-3
150 × 434 (meta-GGA default)91 2169.2e-55.5e-61.0e-44.2e-53.6e-3
150 × 590105 1869.9e-53.6e-66.7e-58.6e-63.5e-3
flat 200 × 590 (reference check)354 0002.4e-54.2e-62.7e-54.1e-51.2e-3

All values in eV. 150 × 434 is the smallest grid tested with every r2SCAN case at or below 1e-4 eV (H2CO: 9.96e-5 TDA, 1.02e-4 Casida); the margin is comparable to the reference's own distance from flat 200 × 590 (2-4e-5 eV).

PBE on the production grid is 4.4e-4 eV from flat 150 × 590, and radial pruning moves it by 1.6e-7 eV. For r2SCAN the radial shells the production grid removes from H (50) and O (66) shift the roots; the radial count matters more than the angular order (99 × 434 is worse than 99 × 302). SCAN is not grid-converged even at flat 150 × 590 (1.2e-3 eV from 200 × 590), so no grid here gets its roots to 1e-4 eV; the 150 × 434 default is 3.6e-3 eV from flat 150 × 590. Compare codes only on the same grid.

A separate, library-only TDA-DFT in ferric-gw/src/tddft.rs uses the same kernel (ferric_dft::lr_kernel) and is pinned against PySCF (ferric-gw/tests/tda_dft_vs_pyscf.rs). The user-facing TDA reproduces it to 1e-8 Ha for HF, PBE and B3LYP.

Polarizabilities and dispersion coefficients

What exists. Static molecular and atom-partitioned polarizabilities, Casimir–Polder \( C_6 \) coefficients from per-atom dynamic polarizabilities \( \alpha^A(i\omega) \), and many-body dispersion (MBD). Three sources feed the \( C_6 \) contraction ([rpa] c6_source):

  • ts: the Tkatchenko–Scheffler single-pole model (default).
  • mbd: self-consistent dipole screening of the TS polarizabilities. This is the full-range screening of Tkatchenko et al. 2012 (Gaussian-damped dipole tensor, no Fermi range separation), not the range-separated screening of MBD@rsSCS.
  • pdep: dynamic PDEP-RPA polarizabilities (examples/water-c6-pdep.toml, examples/argon-c6-rpa-pbe.toml).

MBD@rsSCS dispersion energy. ferric.mbd_rsscs_energy(mol, volume_ratios, functional=... | beta=...) (Rust: ferric_rpa::dispersion::mbd_rsscs_energy) computes the MBD@rsSCS energy of Ambrosetti et al. 2014 for a finite molecule, standalone (no SCF). Inputs are per-atom Hirshfeld volume ratios, which scale the free-atom α, C6 and R_vdW (Z = 1–54). The polarizabilities are screened with the short-range part (1 − f) of the Gaussian-damped dipole tensor on libMBD's 15-point imaginary-frequency grid; the energy couples the screened oscillators through the long-range part f of the bare dipole tensor, with the Fermi function f(R) = 1/(1 + exp(−6(R/(β(R_A + R_B)) − 1))). β is functional dependent: PBE 0.83, PBE0 and HSE06 0.85; any other functional needs an explicit beta. It returns the energy and the screened α₀, C6, R_vdW and ω per atom, and raises on a polarization catastrophe (a non-positive coupled-oscillator eigenvalue). No periodic systems. As a dispersion correction on a Kohn–Sham SCF it is [dft] dispersion = "mbd" / run_dft(dispersion="mbd"), which take the volume ratios from the converged density (reported as DftResult.volume_ratios) and add the energy and its analytic gradient (see SCF and DFT).

The argon-c6-rpa-pbe.toml header records C6(Ar–Ar) = 56.4 a.u. at RPA@PBE/aug-cc-pVTZ against the DOSD value 64.3 (−12%). Which of TS and PDEP-RPA gives better molecular \( C_6 \) is not established.

Use an augmented basis for any polarizability or \( C_6 \): without diffuse functions the dipole response is badly underestimated.

TDHF/RPAx \( C_6 \) is a measured negative. \( C_6 \) built on the RPAx@PBE kernel stays about 63% low regardless of the gap. Its static polarizability is not established either: at a physical scissor (0.36 Ha) water/cc-pVDZ gives an isotropic α of 5.20 a.u. against the DOSD 9.64 a.u. (−46%). The 9.24 a.u. quoted in the example's header comes from scissor = 0, where the tensor has a negative diagonal component, and that setting is refused. method.kind = "tdhf-static-polarizability" computes static α only. It needs a KS reference ([rpa] xc), and at the default [gw] scissor = 0 it can hit an excitonic instability, which is reported as an error rather than a negative α; examples/water-tdhf-static-alpha.toml hits it as shipped, so set scissor to about 0.3–0.4 Ha.

Cite

PDEP: Wilson, Gygi & Galli 2008. RI-RPA quadrature: Eshuis, Yarkony & Furche 2010; minimax grids: Kaltak, Klimeš & Kresse 2014. GW: Hedin 1965; GW100: van Setten et al. 2015. TDDFT review: Dreuw & Head-Gordon 2005. TS: Tkatchenko & Scheffler 2009; MBD: Tkatchenko et al. 2012; MBD@rsSCS: Ambrosetti et al. 2014. Full entries in References.

Constrained DFT

Charge- and spin-constrained DFT, and the electron-transfer couplings that follow from it.

Run it

Python and the Rust library; there is no CLI section.

  • run_cdft(mol, basis_set, constraints, functional=None, ...) returns a CdftResult: a constrained UHF solve, or UKS when functional names a libxc functional other than "HF" (None and "HF", any case, give UHF).
  • CdftConstraint(atoms, target, kind="charge") defines one fragment constraint. atoms are 0-based atom indices. target is the electron population on the fragment, the Becke-weighted trace \( \mathrm{Tr}[W D] \), not a net charge: \( N_\alpha + N_\beta \) for kind="charge" and \( N_\alpha - N_\beta \) for kind="spin". A neutral He atom has a charge population of 2.0; He⁺ has 1.0.
  • cdft_coupling(state_a, state_b) returns a CdftCouplingResult with the Wu–Van Voorhis coupling h_ab, the determinant overlap s_ab and the two diabat energies e_a, e_b. The raw element uses each state's free energy F = E + λN, which makes the coupling independent of a constant shift of the constraint operator.

In Rust the entry points are ferric_scf::cdft_driver::solve_cdft_uhf and ferric_scf::cdft_coupling::coupling_hab; see the Rust API.

This example reproduces the HeNe⁺ constrained solution that crates/ferric-scf/tests/cdft_outer_loop.rs pins (E = −130.4021906 Ha, λ = −2.754 Ha per electron), with the He fragment held at 2.0 electrons:

import ferric

# HeNe+ doublet at 2.0 Å; hold the He atom (index 0) at 2.0 electrons.
mol = ferric.Molecule.from_xyz_string(
    "2\nHeNe+\nHe 0.0 0.0 0.0\nNe 0.0 0.0 2.0\n", 1, 2
)
r = ferric.run_cdft(
    mol,
    ferric.BasisSet.bundled("def2-svp"),
    [ferric.CdftConstraint([0], 2.0, kind="charge")],
    guess="hcore",
    level_shift=0.5,
    scf_max_iter=400,
    lambda_tol=1e-5,
    max_outer=40,
    stability_descent=False,
    grid_radial=99,
    grid_angular=302,
)
print(r.converged)
print(f"{r.energy:.6f}")
print(f"{r.populations[0]:.5f}")
True
-130.402190
2.00000

r.energy is the ordinary UHF/UKS energy at the constrained density, without the constraint term. r.lambdas holds the multipliers (Ha per electron) and r.weight_matrix(i) the AO weight operator of constraint i.

What to know before using it:

  • A returned result has a converged λ loop. When the outer loop exceeds max_outer, run_cdft raises RuntimeError. The inner SCF at the final λ can still be unconverged, so check r.converged: it is true only when the inner SCF converged and every constraint is met to lambda_tol.
  • A constraint is applied at least once before it counts as met. The loop starts at λ = 0, where the state is the unconstrained one. If its population is within lambda_tol of the target but more than 1e-3 electrons away, the loop still takes a Newton step rather than returning the unconstrained state as if it were constrained. Within 1e-3 the unconstrained state is returned with λ = 0: that is the measured spread of one state's population across grids, level shifts and guesses (up to 8.9e-5), with a margin. A loose tolerance can therefore return a state that is only partly constrained, so check r.max_constraint_error (or the per-constraint r.residuals) to see how far from the target it is. The stability-descent restarts follow the same rule.
  • stability_descent defaults to True here (in run_uhf it defaults to False). After the λ loop converges, a constrained saddle point is followed downhill to a lower state that still meets the constraint. The example turns it off to reproduce the pinned solution; with it on, this HeNe⁺ case ends about 0.0245 Ha lower. The descent is skipped for a KS reference.
  • The weight grid defaults to 99 × 302. It must resolve populations below lambda_tol; 75 × 110 resolves them only to about 1e-4. Setting grid_radial or grid_angular uses that grid for the XC quadrature too.
  • The UKS path is the one compared against another code. Constrained UKS/PBE matches NWChem 7.2.2 (see Accuracy). The UHF path (functional=None or "HF") has no comparison for energies along a target scan or for couplings: NWChem's standalone SCF (Hartree–Fock) module has no cDFT, because the Becke weight operator is built on the XC grid of its DFT module. Constrained UHF state selection on HeNe⁺ and LiH⁺ is compared through that DFT module, with Hartree–Fock exchange and a vanishing Slater term extrapolated to zero (ferric-scf/tests/validation_cdft_state.rs).
  • One constraint is the tested case. For one constraint the outer loop is a Newton step kept inside a sign-change bracket. It backs off from a λ whose inner SCF does not converge, and it discards a finite-difference derivative whose two inner solves landed in different SCF states. With several constraints the outer loop is a plain k × k Newton step without these safeguards.
  • cdft_coupling has strict preconditions and raises ValueError when one fails. Each state carries exactly one kind="charge" constraint, both are converged, and both come from the same molecule, geometry, basis, charge and multiplicity, and the same Hamiltonian: functional, df_j_aux/df_k_aux, k_builder, XC grid, point charges and external field. Two states that are the same determinant (\( |S_{ab}| \to 1 \)) also raise. The sign of h_ab is a determinant-phase convention; compare \( |H_{ab}| \).

Accuracy

Constrained UKS/PBE energies and multipliers are compared against NWChem 7.2.2 cdft ... pop becke on LiH, HF and H2O⁺ (charge and spin constraints) at 6-31G and def2-SVP (ferric-scf/tests/validation_cdft.rs): E(N) − E_unconstrained to 2.0e-7 Ha and λ to 1.1e-6 against NWChem's grid limit, and dE/dN = −λ to 2.3e-12 Ha (this identity is checked at def2-SVP, on the first target of each constraint kind); numbers are on Capabilities and validation. No external reference value is stated for UHF-cDFT energies apart from the HeNe⁺ and LiH⁺ states in validation_cdft_state.rs. On HeNe⁺/def2-SVP at R = 2.0 Å the hole-on-Ne state (N_He = 2) is NWChem's lower state once NWChem's Becke partition is given ferric's He/Ne radius ratio (0.667; NWChem's own is 0.700). Against NWChem's native partition it sits 0.563 eV higher. The offset comes from the partition: with NWChem's radii patched into ferric's radius table (a one-off measurement, not a test), ferric reaches −130.447404 Ha against NWChem's native −130.447401 Ha. The He₂⁺ coupling ingredients (determinant overlap, one- and two-electron transition elements, |V|) match NWChem's et module to ≤ 6e-11 Ha on NWChem's own determinants, and the KS diabats and couplings match end to end when started from NWChem's λ, except def2-SVP at 3.50 Å, where inner SCF solves within 1e-9 in λ of the root do not converge in 100 iterations (see Capabilities and validation). These diabats are validated only from NWChem's λ, set through the Rust RhfConfig::cdft_lambda_init; run_cdft starts from λ = 0, where the symmetric pair is delocalized and the inner SCF is bistable. The tests also check the coupling kernel on synthetic matrices and He₂⁺ identities (ferric-scf/tests/cdft_coupling.rs). On HeNe⁺, an asymmetric pair, the same file checks that both diabats carry the requested charge, that the hole-on-He state is reached through either fragment (3.9e-6 Ha apart), that the hole prefers Ne, the lower-IP atom (gap 1.7646 eV with the default stability descent; 1.0975 eV is a saddle point), and that H_ab is invariant under W → W − cS. These run at R = 2.0 Å only: from the hcore start, at 2.5, 3.5 and 4.0 Å the outer loop does not converge or returns the unconstrained state, and from MINAO at 2.5 Å the two routes to the hole-on-Ne state disagree by 6e-4 Ha. |H_ab| is not asserted on HeNe⁺; cdft_coupling_hene.rs probes it over a distance series. The tests also check exact identities on LiH/def2-SVP (cdft_uhf.rs): the constraint is satisfied, λ = 0 reproduces plain UHF, and the constraint composes with an external point charge. The Python tests (crates/ferric-python/tests/test_cdft.py) rerun those configurations through the bindings and check cdft_coupling against an independent transition-density construction. Apart from the HeNe⁺ and LiH⁺ states above, treat UHF-cDFT energies as unvalidated against other codes; see Capabilities and validation.

The response connection

A cDFT constraint couples a Lagrange multiplier \( \lambda \) to a fragment-weighted density operator. The derivative

\[ \frac{\partial N}{\partial \lambda} \]

is the charge moved per unit constraint potential, a susceptibility. cDFT therefore probes the same object as RPA and GW and attenuated MP2, through a different coupling.

Implementation

  • Fragment charge and spin constraints via a grid-Becke weight operator
  • A nested Lagrange-multiplier solve (Wu–Van Voorhis): an inner SCF at fixed \( \lambda \), an outer Newton iteration on \( \lambda \) itself

Each outer step is a full converged inner solve, so cDFT costs several plain SCFs.

Electron-transfer coupling

The coupling \( H_{ab} \) between two charge-localized diabatic states follows from a non-orthogonal determinant overlap, computed via Löwdin biorthogonalization. It is the matrix element governing electron-transfer rates in Marcus theory.

Tolerance and coupling error

The λ convergence tolerance (lambda_tol in Python, cdft_lambda_tol in Rust, default 1e-5 electrons) affects the coupling: a loosely converged \( \lambda \) produces diabatic states that miss their targets, and \( H_{ab} \) inherits that error. Tighten it before trusting a coupling. The exception is a constraint whose population barely responds to \( \lambda \), such as He₂⁺ at the localized-hole plateau with a UHF reference: on He₂⁺/def2-SVP UHF at 2.5–3.5 Å the outer loop does not converge in 40 iterations at 1e-5, while at 1e-2 (what the tests use) it stops after 3 with λ ≈ 0.91 and residuals of 1.6e-3 to 7.8e-3 electrons, so check r.max_constraint_error before trusting the state. With a UKS/PBE reference the He₂⁺ diabats do converge from λ = 0 at 1e-10 (validation_cdft_et.rs).

Cite

Wu & Van Voorhis 2006 (electron-transfer coupling from cDFT); Becke 1988 (the fragment weight partition). Full entries in References.

Periodic systems

ferric runs 3-D periodic Gaussian-basis calculations through the ferric-pbc crate. Conventions follow PySCF pyscf.pbc: Ewald nuclear repulsion with a neutralising background, the dropped-G = 0 Coulomb convention, and the Madelung exchange correction (exxdiv = "ewald"). Energies are Hartree per cell. The equations, with code and test cross-references, are in crates/ferric-pbc/theory.md; the identities are checked by scripts/pbc_ewald_pairft_stress_sympy.py.

What runs

Gamma pointk-point mesh (kmesh)
SCFRHF, UHF, ROHFRHF, UHF, ROHF
Kohn-ShamRKS, UKS, ROKS: LDA, GGA, global hybrids; range-separated (CAM) GGA hybrids; SCAN / r2SCAN / TPSS on the routes listed in the input referenceRKS, UKS, ROKS with the same families (per-route functional limits in the input reference)
CorrelationMP2, dRPA on the RHF reference (open-shell UMP2 / URPA library-only)MP2, dRPA on the RHF reference
Nuclear forcesanalytic, every SCF route; CLI task = "optimize" (atoms move, lattice fixed)analytic; CLI task = "optimize"
Stressanalytic, Python with_stress=Trueanalytic, Python with_stress=True and CLI [cell] stress = true
ECPsyesyes

Meshes are Gamma-centred (centring = "gamma") or shifted Monkhorst-Pack ("mp"). The exchange divergence uses the Gamma Madelung constant, or for a mesh that of the diag(N) supercell.

Two-electron builders

  • jk = "dense" (default): a dense pure-AFT nao⁴ ERI tensor. It is a correctness oracle for toy cells, hard-capped by max_eri_gb (default 0.5 GiB; range-separated hybrids need 40 nao⁴ bytes).
  • jk = "rsgdf": range-separated Gaussian density fitting (PySCF RS-GDF conventions). Requires an auxiliary basis (auxbasis, e.g. "cc-pvdz-ri") and is bounded by the unified memory budget ([memory] budget_gb / memory_budget_gb). Options: gdf_omega (the fit's Ewald split), range_split (energy only), sr_column_rotation.

The nuclear attraction is split at its own omega (default min(2.5 √π/Ω^{1/3}, ω_cap)); any value gives the same energy up to truncation.

Entry points

CLI. Add a [cell] section (lattice, unit, optional kmesh, exxdiv, jk, …) to an input file; method.kind is rhf, uhf, rohf, ksdft, rimp2 (periodic MP2) or pdep-rpa (periodic dRPA). Full key list: Input file, [cell]. Examples: examples/h2-cell-rhf.toml, examples/h2-cell-kpts-rhf.toml, examples/h2-cell-kpts-rhf-stress.toml, examples/h2-cell-rks-opt.toml.

Python. Coordinates and lattice rows in Å, omega/gdf_omega in Å⁻¹:

  • Gamma point: run_rhf_gamma, run_uhf_gamma, run_rohf_gamma, run_rks_gamma, run_uks_gamma, run_roks_gamma, run_mp2_gamma, run_drpa_gamma.
  • k-point mesh: run_rhf_kpts, run_uhf_kpts, run_rohf_kpts, run_rks_kpts, run_uks_kpts, run_roks_kpts, run_mp2_kpts, run_drpa_kpts.
  • auto_gdf_omega chooses an RS-GDF split.

SCF drivers take with_gradient / with_stress; results expose energy, e_nuc (Ewald), madelung, gradient() (Hartree/Bohr per cell) and stress() (σ = (1/Ω) dE/dε, Hartree/Bohr³, not symmetrised; pressure −tr σ/3). Signatures and refusals are in ferric.pyi.

import ferric
mol = ferric.Molecule.from_xyz("testdata/molecules/h2.xyz")
a = 4.0 * 0.529177210903  # 4 Bohr cube, in Angstrom
res = ferric.run_rhf_gamma(
    mol, [[a, 0, 0], [0, a, 0], [0, 0, a]],
    ferric.BasisSet.bundled("sto-3g"), exxdiv="ewald", with_stress=True,
)
print(res.energy, res.stress())

Limits

  • 3-D periodicity only; no slabs, wires or 2-D Coulomb truncation.
  • Charged cells are an error.
  • No lattice-vector relaxation: task = "optimize" moves atoms at a fixed lattice. task = "frequencies" is an error.
  • Gamma-point stress is Python-only; the CLI stress = true requires kmesh.
  • The dense AFT builder is toy-scale by construction (memory 8 nao⁴ bytes, G count growing as Ω p_max^{3/2}); production-size cells need jk = "rsgdf".
  • VV10 is refused on every periodic derivative path; per-functional and per-route support for forces and stress is listed in the input reference and Capabilities and validation.
  • Open-shell forces and stress refuse a non-stationary SCF state rather than return a number: ROHF/ROKS orbital gradient above 1e-5 Ha, UKS spin commutator above 1e-5 Ha (k-point), a stored Fock that differs from the rebuilt one by more than 1e-8 Ha, or SCF k-points that are not the mesh's. Converge the SCF tighter (density_conv 1e-10 is FD-grade). Derivation and the full list of tolerances: crates/ferric-pbc/theory.md section 6.
  • Gamma-point local MP2 is library-only and its default pair gate keeps every pair below a volume onset.

Electronic response

ferric is organized around electronic response: how the electron density reacts to a perturbation. Standard quantum-chemistry codes are usually organized around a hierarchy of wavefunction ansätze (HF → MP2 → CCSD → CCSD(T)). ferric uses response as the organizing principle instead.

The central object appears under several names depending on which coupling you look at:

ObjectDefinitionWhere it appears in ferric
Density response \( \chi \)\( \chi = \delta\rho / \delta v_{\text{ext}} \); \( \chi_0 \) is its independent-particle formRPA, GW, MP2's dispersion
Dielectric matrix \( \varepsilon \)\( \varepsilon = 1 - v\chi_0 \) (RPA), with \( v \) the Coulomb kernelPDEP-RPA and GW screening
Polarizability \( \alpha \)response of the dipole to a uniform field; \( \alpha(i\omega) \) gives \( C_6 \)polarizabilities, dispersion coefficients
\( \partial N / \partial \lambda \)charge moved per unit constraint potentialconstrained DFT

These are the same physics viewed through different couplings. A code that computes one well should be able to compute the others, and errors in one should be diagnosable as errors in the others.

The claim

The premise behind the architecture is that response is:

  1. Local in real space: a density fluctuation here does not much affect the density far away, so the response should be sparse in a localized basis.
  2. Low-rank in its eigenspectrum: the dielectric matrix has a small number of dominant eigenmodes, so it can be compressed without losing the physics.

If both hold, organizing the computation around response should make it cheaper: attenuate the operator, keep the dominant dielectric modes.

What is demonstrated

Low rank: demonstrated, with a narrower scope. PDEP compresses the dielectric matrix to its dominant eigenpotentials in the RI auxiliary space, and that works in production paths; see RPA and GW. It does not remove the sum over empty states: ferric builds \( \chi_0 \) by summing over every occupied–virtual pair, and PDEP compresses what comes out of that sum.

Locality: one positive result, still without a speedup.

  • AO-sparse Laplace SOS-MP2. Restricting each localized orbital's pseudo-density to an AO domain works: the radius needed grows far more slowly than the molecule (chemical accuracy at 3 to 5 Bohr from ethane to dodecane, while radius/diameter falls from 0.52 to 0.17; within 0.05% at 4 Bohr on a 71-atom drug molecule). The tensor algebra is still dense, so no timing gain is claimed. This result is specific to that formulation and does not carry over to the other locality approaches below. The test sos_ao_sparse_truncation_radius_is_transferable_across_sizes pins the STO-3G butane/octane comparison (12 Bohr exact on both; octane worse at 3 Bohr). The C2-C12 sweep and the drug-molecule figure are measurements, not regression tests.
  • Local MP2 (amplitude threshold) has localized virtuals and per-pair domain-local RI fits. The integral-direct variant (rimp2 with [local] integral_direct = true) is measured at about N1.24 (erfc) to N1.4 (Coulomb) on alkanes C20–C48, three points in one basis, so the reading is provisional. Local MP2 without integral_direct still builds the global 3-index tensor and makes no scaling claim.
  • RI-Laplace MP2 is dense; it is the correctness reference for the AO formulation, not a reduced-scaling path.

The locality premise is therefore partly supported and not yet cashed in as cost. Measured limits are kept on Capabilities and validation.

Why this framing is useful anyway

Even where the scaling payoff has not arrived, the response framing makes the error in one method diagnosable through another.

MP2's dispersion is the clearest case. Its dispersion energy is built from an uncoupled Hartree–Fock response, in which the density fluctuation does not feel the field it creates. For systems with low-lying, highly polarizable excitations, such as π-stacked and other π systems, that uncoupled response over-polarizes, and MP2 overbinds. This is documented in the literature that replaces MP2's uncoupled dispersion with a coupled one (Cybulski & Lytle 2007; Heßelmann 2008; Pitoňák & Heßelmann 2010). The size of that coupling correction varies from system to system, and ferric has no coupled-dispersion (MP2C-style) implementation of its own, so read this as the literature's diagnosis, not a ferric measurement.

Attenuated MP2 takes a blunter route: it removes the long-range correlation altogether and relies on a fitted short-range operator plus the basis-set error it is fitted in. That is why it has zero asymptotic \( C_6 \), why its parameters belong to one basis, and why MP2-V adds long-range dispersion back through VV10. RS-MP2 + LR-RPA puts the long-range part back through response instead. See The MP2 family.

Where the methods come from

Each method family in ferric works on the response function from a different direction. The definitions and parameters live on the method pages; this page is the map.

Attenuated MP2: removing the long-range part

MP2's dispersion comes from an uncoupled response, which over-polarizes for systems with low-lying, highly polarizable excitations (π-stacked aromatics are the classic case); it is not a general property of polarizable molecules. In small basis sets the resulting overbinding is partly cancelled by basis-set superposition error, which disguises it.

Attenuated MP2 replaces \( 1/r \) in the correlation energy with a short-range operator (erfc or terfc) and fits its range to interaction energies in a chosen basis. It removes the long-range correlation entirely rather than correcting it, so it has no asymptotic \( C_6 \), and its parameters are specific to the basis and protocol they were fitted in. MP2-V restores long-range dispersion with VV10; RS-MP2 + LR-RPA restores it with long-range RPA.

Goldey & Head-Gordon (JPCL 2012) introduced it in aug-cc-pVDZ; Goldey, Dutoi & Head-Gordon (PCCP 2013) introduced terfc in aug-cc-pVTZ; the dual-attenuated SCS variant is Goldey & Head-Gordon (JPCB 2014). Operators, parameters and fitting protocol: The MP2 family.

PDEP-RPA and GW: compressing the response

The dielectric matrix is built from the density–density response function. ferric forms the independent-particle response in the RI auxiliary basis by summing over occupied–virtual pairs, then works in the eigenbasis of the static dielectric matrix, dropping eigenpotentials that carry almost no screening. PDEP (projective dielectric eigenpotentials) is that compression. It is the demonstrated part of the low-rank premise; the empty-state sum that feeds it is still there. See RPA, GW and excited states.

Constrained DFT: reading the response

A cDFT constraint couples a Lagrange multiplier \( \lambda \) to a fragment-weighted density operator. The derivative \( \partial N / \partial \lambda \), how much charge moves per unit constraint potential, is a susceptibility.

That makes cDFT a direct probe of the same object. It also yields charge-localized diabatic states whose electron-transfer couplings \( H_{ab} \) follow from non-orthogonal determinant overlaps. See Constrained DFT.

What this buys

Three families, one object. An error in the polarizability shows up as an error in dispersion, in screening, and in charge-transfer coupling, so a fix validated in one place has predictable consequences in the others.

That is the design bet. Whether it pays off in cost is still open; see Electronic response for what has and has not been measured.

Architecture

A Cargo workspace of focused crates, layered so that method crates depend on integrals and core, not on each other.

                          +------------------+
                          |   ferric-cli     |   TOML config -> most methods
                          +--------+---------+   (+ ferric-python: pyo3 bindings)
                                   |
   +-----------+-----------+-------+------+-----------+------------+
   |           |           |              |           |            |
+--v----+ +----v----+ +----v----+   +-----v----+ +----v-----+ +---v------+
|ferric | |ferric   | |ferric   |   |ferric    | |ferric    | |ferric    |
|-scf   | |-mp2     | |-dft     |   |-rpa      | |-gw       | |-cc       |
|RHF/UHF| |RI-MP2,  | |RKS/UKS/ |   |PDEP-RPA, | |G0W0,     | |CCD/CCSD/ |
|/ROHF, | |OO,att,  | |ROKS,    |   |U-PDEP,   | |COHSEX,   | |(T)       |
|KS-DFT,| |SCS,     | |libxc,   |   |response  | |evGW,     | +----------+
|DIIS,  | |2terfc,  | |Becke    |   |props,    | |U-GW      |
|MOM,AH,| |Laplace  | |grids,   |   |ESP/Hirsh/| +-----+----+
|cDFT,  | +----+----+ |VV10     |   |NPZ export|       |
|grads  |      |      +----+----+   +-----+----+       |
+---+---+      |           |              |            |
    |          +-----+-----+------+-------+------------+
    |                |     |      |
    |   +------------v--+ +v------v-----+   ferric-tensors (sparse),
    |   |ferric-export | |ferric-      |   ferric-quadrature (Laplace/grid roots)
    |   |cube,NPZ,GTO  | |integrals    |   support crates
    |   +--------------+ |libint2 FFI  |
    |                    |shim/shim.cc |   Coulomb/erf/erfc, 1e/2e/3c/2c, derivs
    +--------+-----------+------+------+
             |                  |
        +----v------------------v----+
        |        ferric-core         |   Molecule, BasisSet, Shell, elements,
        |                            |   BSE-JSON / G94 parsers, bundled bases
        +----------------------------+

Layers

ferric-core: molecular structure, basis sets, shells, elements, and the BSE-JSON / Gaussian-94 parsers. Also the home of shared infrastructure: configuration (ConfigVar), memory budgets (MemoryPlan), the BLAS-thread hazard model, and MPI context.

ferric-integrals: the libint2 FFI and its C++ shim. Coulomb, erf and erfc operators; 1-electron, 2-electron, 3-center and 2-center integrals; first derivatives. The shim wraps every libint2 call in try/catch and returns a sentinel, so a C++ exception never unwinds across the FFI boundary.

Method crates: ferric-scf, ferric-mp2, ferric-dft, ferric-rpa, ferric-gw, ferric-cc, ferric-ci, ferric-tddft, ferric-pcm, ferric-mm, ferric-xtb, ferric-d3 (Grimme D3(BJ) dispersion energy and gradient, optional ATM three-body term).

Support: ferric-tensors (the einsum! contraction macro), ferric-quadrature (Lebedev grids, minimax Laplace roots), ferric-export (cube files, NPZ, GTO evaluation).

Interfaces: ferric-cli (TOML-driven) and ferric-python (pyo3).

Cross-cutting conventions

Threading. rayon owns outer parallelism; BLAS is pinned to one thread inside any rayon worker, enforced at runtime rather than by convention. Throughput across many jobs comes from many single-threaded processes, not one wide job.

Determinism. Reductions fold in a fixed ascending order independent of thread count, so results are bit-identical across RAYON_NUM_THREADS. This is pinned by tests. Another deterministic order, such as a tree-fold, would change the bits, because floating-point addition is not associative.

Memory. MemoryPlan expresses what a path will allocate and when. An allocation that exceeds the budget and has no spill-to-disk or recompute fallback is refused before allocating, with a breakdown naming the dominant term. The CLI installs one process-wide MemoryPool sized from the resolved budget, and the large, size-dependent allocations reserve their bytes from it, so two allocations alive at the same time cannot each claim the whole budget. Library and Python callers install no pool; each check then compares its own allocation with the whole budget. Allocations that have a fallback spill to disk or are recomputed instead of being refused, and basis-sized matrices, engines and scratch are not charged (see Sharp bits). Guards are tested in both directions: a starved budget must be refused (or must fall back), and an ample budget must still run.

Errors. Methods return Result; iterative solvers additionally carry a converged flag, since non-convergence is a result rather than an error. Callers are expected to check it.

Capabilities and validation

Implemented ≠ validated. Working code is not a checked number.

Read this page before trusting any result. It lists every CLI method.kind (which references it accepts, which tasks it supports, the matching Python function, an example input) and the Python entry points. Each graded CLI method.kind gets a grade (three kinds are ungraded, and Python entry points are not graded individually), and the page shows how far each grade's numbers have been checked against an independent reference and where they are known to fail.

How to read it: find the capability in the matrix or the Python entry points. A matrix Grade cell links to the Anchors table when a row there holds the evidence; the Known limits section records the measured negatives. Every matrix cell comes from the CLI dispatch in crates/ferric-cli/src/lib.rs or the bindings in crates/ferric-python/src/lib.rs. The page is maintained by hand, so check those two files if a cell looks wrong. For keyword details see the input reference; for the example files see the examples index.

Grades

The grades are the ones ferric's CLI uses. Every CLI method.kind except laplace-sos-mp2 has a grade. A kind's grade is for the exact method; the local approximation of rimp2, drpa and linlccd (the [local] section) is graded separately in that row's Caveat. The CLI prints the Smoke and Spike grades as a [warning] line on stderr at run time; Proven kinds and the ungraded kind print nothing. The warning text is the EPISTEMIC_WARNINGS table in crates/ferric-cli/src/lib.rs, and the "Caveat" column of the matrix condenses it. The grades themselves are METHOD_KIND_GRADES in crates/ferric-core/src/certificate.rs, a copy of the Grade column below that a test keeps equal to it.

rhf, uhf, rohf and ksdft energy runs print a certificate block after the energy and write it to the JSON run log (run_end.extra.certificate; a certificate record for the open-shell runs); Python RhfResult, UhfResult and DftResult carry it as .certificate. It states the kind's grade, the build, the approximations in effect (RI-J / RI-K, LinK / COSX, the XC grid, a level shift, ECPs, embedding, solvation) and warnings: a Plateau exit, a non-Stable stability verdict, and an unchecked UHF/UKS stability. An unconverged SCF is capped to not graded.

Post-SCF kinds print a certificate block after their result: the MP2 family (rimp2 exact, local and open-shell, mp3, oo-rimp2, att-rimp2, mp2-v, scs-mp2, scs-mp2-2terfc, laplace-mp2, laplace-sos-mp2), rs-mp2-rpa, drpa, pdep-rpa, ccsd, ccd, ccsd(t), linlccd, wb97x-l-v, b2plyp, dsd-pbep86, tda, tddft, gw, bse-tda and tdhf-static-polarizability. Kinds that write a result record carry it as result.components.certificate; pdep-rpa, wb97x-l-v, b2plyp, dsd-pbep86, tda, tddft, gw, bse-tda and tdhf-static-polarizability write a certificate record. Python RiMp2Result, Mp3Result, OoRiMp2Result, AttenuatedMp2Result, ScsMp2Result, Mp2VResult, LaplaceMp2Result, SosMp2Result, RsMp2RpaResult, CcResult, PdepRpaResult, GwResult, UGwResult, BseResult, TdhfStaticPolarizabilityResult, DoubleHybridResult and TddftResult carry it as .certificate; the run_drpa and run_linlccd dicts carry a "certificate" key. The grade is the kind's row, capped at the grade of the SCF reference's certificate, whose approximations and warnings it repeats as scf <name> and scf reference: .... It records the auxiliary basis, frozen core, a non-Coulomb correlation operator and each method's settings (SCS scales, Laplace points, frequency quadrature, PDEP truncation, GW variant, double-hybrid scaling or λ and ω, the TDDFT response grid). Caps to not graded: an [local] amplitude threshold at finite eps, an unconverged orbital optimization (oo-rimp2), drCCD fixed point (drpa) or evGW eigenvalue loop. Warnings: GW and BSE-TDA runs at settings other than n_quad = 100, trunc_thresh = 0, unconverged quasiparticle roots, suspect quasiparticle energies, an unconverged PDEP eigensolver. Periodic runs (the CLI [cell] section and the Python Gamma-point and k-point results) carry a certificate with the grade not graded.

GradeMeaning
ProvenTotal energies (or the stated property) agree at a stated tolerance with an independent reference, pinned by tests. An independent reference is another code (PySCF, MOLGW), a numpy reference, a published value, or an exact limit the method must reduce to. The [warning] line treats "Proven" and "Proven (narrow)" alike; the certificate names the grade.
Proven (narrow)As Proven, but only on the stated class of systems, or only against the stated kind of reference (for example exact limits only)
SmokeRuns end to end, and some pieces are checked, but there is no independent reference for the headline number, or only one loose one. The checks are range bands, internal consistency or limits rather than a tight external number. Do not quote a Smoke result as a reference value.
SpikeBuilt on new infrastructure and not yet compared against any reference code; for exploration only
not gradedDispatched by the CLI with no recorded grade, so it prints no warning; treat it as unproven

Symbols: ✓ = supported. — = not supported (the CLI refuses it with an error). FD = finite differences of the analytic gradient (6N gradient evaluations). The analytic Hessian covers closed-shell RHF and UHF of any multiplicity (both also with RI-J and/or RI-K, without an external potential; all with ECPs), and ROHF with exact J/K or RI and no external potential, with a basis up to f functions; rhf, uhf and rohf frequencies use it there by default.

CLI method.kind matrix

"Reference" is the SCF reference the CLI builds for that kind. Open-shell support is listed only where the dispatch code handles it (see Open shells below).

method.kindFamilyReference (CLI)Energytask = "optimize"task = "frequencies"PythonExampleGradeCaveat
rhfSCFRHF; RKS with [dft] functional (refuses multiplicity > 1)✓✓ analyticanalytic (RHF, exact J/K, ECPs included, up to f; RKS through the moving-grid KS Hessian, as for ksdft); FD otherwiserun_rhf, run_optimize, run_frequencieswater-rhf.tomlProven—
uhfSCFUHF; UKS with [dft] functional✓✓ analytic (+ D3(BJ) or MBD@rsSCS gradient with [dft] dispersion on UKS)analytic (UHF, exact J/K, ECPs included, up to f); FD otherwise; with [dft] dispersion on UKS, FD of the KS + dispersion gradientrun_uhf, run_frequencies(reference="uhf", xc=..., dispersion=...)h_uhf.tomlProven—
rohfSCFROHF; ROKS with [dft] functional✓✓ analytic (+ D3(BJ) or MBD@rsSCS gradient with [dft] dispersion on ROKS)analytic (ROHF, exact J/K or RI, ECP allowed, no external potential, up to f); FD otherwise, and for ROKS (+ the dispersion gradient with [dft] dispersion on ROKS)run_rohf, run_frequencies(reference="rohf", xc=..., dispersion=...)nh2-rohf-frequencies.tomlProven—
ksdftSCF/DFTRKS; UKS when multiplicity > 1✓✓ analytic (+ D3(BJ) or MBD@rsSCS gradient with [dft] dispersion, RKS and UKS)analytic moving-grid (RKS LDA/GGA, pure or global hybrid, exact or RI J/K, no ECP or external potential, unpruned grid, response AO table within its cap); FD otherwise (UKS, meta-GGA, VV10, RSH, …); with [dft] dispersion FD of the KS + dispersion gradient (RKS and UKS)run_dft / run_ksdft, run_frequencies(xc=..., dispersion=...)benzene-dfb3lyp.toml, h2-lda-opt.tomlProven—
rimp2MP2RHF; UHF + unrestricted RI-MP2 when multiplicity > 1 (energy only)✓✓ analytic (Z-vector; closed shell only)—run_rimp2 (UHF + UMP2 when multiplicity > 1)water-rimp2.toml; local: water-rimp2-local.toml, alkane8-rimp2-local-direct.tomlProvenExact RI-MP2. With [local] scheme = "amplitude-threshold" (run_rimp2(local=..., eps=...)): Proven (narrow) by its exact limit — at ε = 0 the in-core and integral_direct paths match PySCF DF-MP2 to ≤1.38e-11 Ha (H2O and n-butane, 6-31G and cc-pVDZ), at the default PCG stop of relres 1e-6; finite ε is a measured error map, not validated; closed shell and task = "energy" only; ε = 0 reproduces exact rimp2; the exact reference and the error against it are opt-in ([local] reference = true, compute_reference=True). integral_direct = true never forms the global 3-index tensor; its measured scaling is under Known limits. On the device ([gpu]) the energy stage runs with B_ov resident: at precision = "f64" (the default) it agrees with the CPU path to within a bound on the difference of summation order (gpu_rimp2_resident.rs); with precision = "mixed" and rimp2-energy in mixed_kernels it is a measured error map, not Proven (f32-resident B_ov, f64 accumulation; closed-shell and unrestricted, Coulomb, erfc and terfc operators), graded under Mixed-precision GPU kernels.
mp3MP2RHF✓——run_mp3water-mp3.tomlProven—
oo-rimp2MP2RHF; UHF + unrestricted OO-RI-MP2 when multiplicity > 1 (energy only)✓——run_oo_rimp2 (closed shell only)water-oo-rimp2.tomlProven (narrow)Energy matches an independent numpy OO-RI-MP2 to 7.5e-13 Ha (closed-shell H2O and NH3, UHF CH3 / cc-pVDZ) and the closed-shell analytic gradient matches a finite difference of its own energy to 8e-9 Ha/Bohr. ORCA 6.1.1 stops 3.7e-8 to 7.0e-8 Ha above the same minimum.
att-rimp2MP2RHF✓——run_attenuated_rimp2water-attmp2.tomlProven—
mp2-vMP2RHF; UHF when multiplicity > 1 (energy only)✓——run_mp2_v (closed shell only)water-mp2v.tomlProven (narrow)The implementation is Proven (narrow): the damped and undamped VV10 half, the terfc-attenuated MP2 half (the published operator) and the erfc control each match an independent PySCF/numpy reference on H2O (cc-pVDZ, aug-cc-pVDZ) and NH3 (cc-pVDZ); the VV10 half and the erfc control also on the S22 water dimer (aug-cc-pVDZ). The terfc E_c itself is not re-validated as an operator here (that is the SCS-MP2(2terfc) row); this row adds its wiring through AttVv10Config::mp2_v_terfc_atz (r₀ in Å → Bohr → ω, metric, frozen core) and the unrestricted path (see Anchors). The parameterization is not benchmarked: the defaults (r₀ = 1.00 Å, b = 11.0, C = 0.0089) are fitted for aug-cc-pVTZ with frozen core and no counterpoise, only an aug-cc-pVTZ subset of S66 has been run (n=5: H-bonded and mixed dimers, S66x8 geometry and references, no CP-anchored reference), and no published MP2-V total energy exists to compare against, so another basis, frozen_core = 0 (the CLI default) or a different system class is unparameterized extrapolation. Open shell is checked only as a UHF singlet collapsed onto RHF; no open-shell parameterization exists (S66 is closed-shell).
scs-mp2MP2RHF✓——run_scs_mp2water-scs-mp2.tomlProven—
scs-mp2-2terfcMP2RHF✓——run_scs_mp2_2terfcwater-scs-mp2-2terfc.tomlProven (narrow)No other code implements terfc. The terf/terfc integrals and the E_OS/E_SS/Eq. 12 energies match an independent Fourier-space construction (H2O, NH3 / cc-pVDZ; H2O / aug-cc-pVDZ). The published parameters are fitted for aug-cc-pVTZ with frozen core and no counterpoise; Only an aug-cc-pVTZ S66 subset (n=5, H-bonded/mixed, S66x8 geometry) has been run; there is no full-set statistic. Needs the terfc tables (FERRIC_TERF_TABLE_DIR, or the repository's terf-tables/).
laplace-mp2MP2RHF✓——run_laplace_mp2water-laplace-rimp2.tomlProvenMinimax-Laplace denominators on five systems (R = 19.0 to 45.7) against exact DF-MP2 and against a quadrature-isolated reference that separates quadrature error from implementation error. See the Anchors table.
laplace-sos-mp2MP2RHF✓——run_laplace_sos_mp2water-laplace-sos-mp2.tomlnot gradedWith c_os = 1.0 it reproduces the opposite-spin MP2 energy (internal reference).
pdep-rpaRPA/GWRHF, or RKS via [rpa] xc; UHF/UKS when multiplicity > 1 (energy only)✓✓ (closed-shell RHF reference only; [rpa] xc is refused; analytic SCF + FD correlation)—run_pdep_rpa (closed shell only; open-shell U-PDEP-RPA is CLI-only)water-pdep-rpa.tomlProvenOpen shell: Lanczos/Davidson, Dense/Laplace χ₀ and run_diagnostics; full rank, Lanczos ≡ Davidson ≡ the full-basis U-RI-dRPA diagnostic and a UHF singlet ≡ the closed-shell path (u_pdep_rpa.rs); chi0_sparsity = "boys" (or "auto" resolving to it) is refused.
rs-mp2-rpaMP2 / RPARHF✓——run_rs_mp2_rpawater-rs-mp2-rpa.tomlSmokeThe ω→0 and ω→∞ limits reduce exactly to MP2 and MP2+dRPA and are Proven. At production ω it is only marginally benchmarked on one small subset and is unproven on new systems.
gwRPA/GWRHF, or RKS via [rpa] xc; UHF/UKS, or ROHF/ROKS with [gw] reference = "rohf", when multiplicity > 1 (energy only)✓ (QP energies)——run_gw, run_u_gwwater-g0w0-pbe.toml, oh-ugw.tomlSmokeMatches PySCF gw_ac/ugw_ac only at matched settings (G0W0@HF and @PBE ≤1.3e-7 Ha, evGW ≤5.7e-7, ECP ≤2.5e-6, U-G0W0@UHF ≤7e-6 Ha; U-G0W0@UKS/PBE, with Σx − v_xc inside each spin's quasiparticle equation, ≤2.6e-8 Ha; U-G0W0 on a semi-canonicalized ROHF reference in the Anchors; the ROKS reference is not compared) with [rpa] n_quad = 100 and trunc_thresh = 0; the CLI defaults are coarser (the 20-point grid moves the H2O G0W0@PBE HOMO by 13 meV) and truncation is not validated. See the G0W0 anchors.
bse-tdaRPA/GWRHF only (refuses multiplicity > 1)✓ (excitations)——run_bse_tdawater-bse-tda.tomlSmokeThe lowest five singlets match an independent numpy BSE-TDA to 1.8e-10 Ha given the same quasiparticle energies (H2O cc-pVDZ and aug-cc-pVDZ, NH3 and CH2O cc-pVDZ; see anchors). The quasiparticle energies come from the internal G0W0, which matches PySCF only at n_quad = 100, trunc_thresh = 0; the defaults are coarser.
tdhf-static-polarizabilityRPA/GWRKS only ([rpa] xc required)✓ (static α)——run_tdhf_static_polarizabilitywater-tdhf-static-alpha.tomlSmokeStatic α only. The kernel is statically screened RPAx, not bare TDHF. Its implementation matches an independent numpy assembly of the same kernel to 5.3e-9 relative, and its bare-exchange limit matches PySCF CPHF (see anchors), at matched settings: exact-J SCF and full-rank PDEP (trunc_thresh = 0; the CLI default is 1e-4). The physics is not established: at a physical scissor (0.36 Ha) α is 5.20 a.u. for water/cc-pVDZ against DOSD 9.64 (−46%), and the independent kernel gives the same 5.20. The same kernel gives C6 about 63% low. At scissor = 0 it can hard-error on a negative α diagonal, which the independent kernel reproduces (water/cc-pVDZ α_xx = −2.68); set [gw] scissor to about 0.3–0.4 Ha.
ccsdCCRHF (spin-adapted solver)✓——run_ccsdwater-ccsd.tomlProven—
ccdCCRHF only (refuses multiplicity > 1)✓——run_ccdwater-ccd.tomlProven (narrow)RI-CCD, spin-orbital solver.
ccsd(t)CCRHF only (refuses multiplicity > 1)✓——run_ccsd_twater-ccsd-t.tomlProven (narrow)Spin-adapted CCSD + spin-adapted (T). Prints E_CCSD, E_(T) and the total.
linlccdCCRHF only (refuses multiplicity > 1; open-shell LinLCCD(hh) is library-only)✓——run_linlccdwater-linlccd.toml; local: water-linlccd-local.tomlProven (narrow)No other code implements LinLCCD(hh); the energy matches an independent numpy solve on PySCF density-fitted integrals to ≤1.2e-12 Ha (H2O and NH3 through the CLI's closed-shell path; UHF OH through the library-only open-shell path). With the hole–hole ladder off it reduces exactly to RI-MP2, and with exact integrals its driver terms reproduce canonical MP2; size consistency is checked. [mp2] linlccd_variant (hh, drivers-only, full) applies exact and local. With [local] (amplitude threshold): Proven (narrow); at ε = 0 every variant, full included, matches the independent numpy solve to ≤ 4.4e-13 Ha, as does the exact path.
drpaRPA/GWRHF only (refuses multiplicity > 1)✓——run_drpa, run_drpa_scan (local ε scan)water-drpa.toml; local: water-drpa-local.tomlProven (narrow)Exact dRPA@HF by the drCCD Riccati solve with nothing truncated; equals the canonical plasmon formula (≤1e-12 Ha), full-rank PDEP (pdep-rpa, trunc_thresh = 0), and an independent numpy plasmon dRPA on PySCF integrals (≤3.0e-11 Ha; water and n-butane at 6-31G and cc-pVDZ, n-octane at 6-31G; see Anchors). Its memory grows as no³·nv² and a run that cannot fit is refused before the SCF. With [local] (amplitude threshold, eps or eps_sweep): Proven (narrow) by its exact limit; the finite-ε error is not variational, and on n-octane it is positive and grows with ε at every point measured (Anchors).
wb97x-l-vCC § ωB97X-L-VIts own RKS (wB97X-L-V) reference (refuses multiplicity > 1; open shell is library-only)✓——nonewater-wb97xlv.tomlProven (narrow)E_KS and λ·E_c against PySCF with the paper's parameters plus a numpy LinLCCD(hh) (water, OH / def2-SVP); Be₂ bond energy against the paper's Table 4.
b2plypCC § double hybridsIts own RKS reference✓——run_double_hybrid(kind="b2plyp")water-b2plyp.tomlSpikeWeighted B88+LYP reference. Not compared with any reference code.
dsd-pbep86CC § double hybridsIts own RKS reference✓——run_double_hybrid(kind="dsd-pbep86")—SpikeWeighted PBE+P86 reference. Not compared with any reference code.
tdaRPA/GW § TDDFTRHF (CIS), or RKS via [tddft] xc (refuses multiplicity > 1)✓ (excitations)——run_tddft(method="tda")water-tda.tomlProven (narrow, closed shell)The lowest five roots match PySCF TDA for water, formaldehyde and NH3 at 6-31G and aug-cc-pVDZ with HF, LDA, PBE and B3LYP: at most 8.1e-4 eV (formaldehyde/aug-cc-pVDZ B3LYP, pruned-grid error), test bar 1e-3 eV; B3LYP/aug-cc-pVDZ on the reference's own flat 75 × 110 grid ≤ 2.8e-6 eV (bar 2e-5 eV). τ-only meta-GGAs (SCAN, r2SCAN, TPSS) take the τ kernel; r2SCAN matches PySCF on water/6-31G only, on the flat 75 × 110 grid (2.1e-6 eV). Meta-GGA runs default to a 150 × 434 grid, on which r2SCAN water roots sit within 9.2e-5 eV (6-31G) and 5.5e-6 eV (aug-cc-pVDZ) of a flat 150 × 590 grid (75 × 110: 2.1e-3 eV); SCAN is not grid-converged at 1e-4 eV on any grid tested (see Known limits). A [tddft] xc with no f_xc kernel (VV10, range-separated) is refused before the SCF.
tddftRPA/GW § TDDFTRHF (TDHF), or RKS via [tddft] xc (refuses multiplicity > 1)✓ (excitations)——run_tddft(method="casida")water-tddft-pbe.tomlProven (narrow, closed shell)Same comparison of the lowest five roots against PySCF TDDFT, same bar. Same functional refusals as tda.

task = "optimize" is accepted only for rhf, ksdft, uhf, rohf, pdep-rpa and rimp2. task = "frequencies" is accepted only for rhf, ksdft, uhf and rohf. On an open-shell molecule both tasks are accepted only for uhf, rohf and ksdft (UHF/UKS, ROHF/ROKS). Both tasks refuse [dft] grid_prune, and [scf] k_builder = "cosx" except for rhf, ksdft, uhf and rohf (the overlap fit with a functional, and for rohf, is refused before the SCF); a COSX final pass is skipped for them. The RKS, UKS and ROKS optimizers and frequency drivers apply D3(BJ) and MBD@rsSCS (energy and exact gradient); an open-shell task = "energy" single point applies either. Any other combination exits with an error before the SCF runs.

Open shells in the CLI

  • uhf and rohf read [molecule] multiplicity directly. With [dft] functional they run UKS and ROKS, for energies, optimizations and frequencies.
  • ksdft with multiplicity > 1 runs UKS (the uhf route with the functional set). For ROKS use rohf with [dft] functional.
  • rhf refuses multiplicity > 1 and points to uhf/rohf.
  • rimp2 and oo-rimp2 run on the same plain UHF that kind = "uhf" runs, then take unrestricted RI-MP2 (UMP2, as PySCF mp.MP2(uhf)) and unrestricted OO-RI-MP2. [mp2] kappa is refused on an open shell. task = "energy" only: there is no unrestricted MP2 nuclear gradient.
  • pdep-rpa, gw and mp2-v solve UHF with MOM after 5 iterations when multiplicity > 1, for task = "energy" only. For pdep-rpa and gw, setting [rpa] xc makes that reference UKS; gw with [gw] reference = "rohf" uses ROHF (ROKS with [rpa] xc) instead, semi-canonicalized per spin; U-G0W0 on a semi-canonicalized ROHF reference is in the Anchors, the ROKS reference is not compared. mp2-v does not read [rpa] xc and stays UHF.
  • linlccd and wb97x-l-v refuse an open-shell molecule; their open-shell versions are library-only (ferric_cc::linlccd_u::u_linlccd, ferric_cc::double_hybrid::u_solve_wb97x_l_v).
  • ccd, ccsd and ccsd(t) refuse an open-shell molecule, and no open-shell CCD, CCSD or CCSD(T) exists in the library either: every CC solver reads restricted orbitals.
  • Every other kind refuses multiplicity > 1 with an error before any integral is computed.

Python entry points

These are Python functions without a method.kind of their own; some rows also have a CLI route through a TOML section or task, noted in the row. Scope is taken from the binding code and its docstrings. They are not graded individually.

CapabilityPythonScope (verified in code)
Open-shell KS frequenciesrun_frequencies(reference="uhf"|"rohf", xc=...)Setting xc promotes RHF/UHF/ROHF to RKS/UKS/ROKS. FD Hessian. Also CLI: kind = "uhf"/"rohf" with [dft] functional and task = "frequencies".
Transition-state searchrun_saddleP-RFO. Closed shell only (refuses multiplicity ≠ 1). Raises if the start has no negative mode. Costs 2(6N+1) + (steps+1) gradients.
Reaction pathrun_ircBoth IRC branches from a saddle's imaginary mode. Closed shell only.
Geometry optimization (Python)run_optimizeRHF only (no xc argument). Accepts point charges and a field.
QM/MM energy + forcesQmmmSystem, run_qmmmmethod = "rhf"/"uhf"/"rks"/"uks". Link atoms, boundary schemes (keep/delete-host/rc/rcd), Gaussian-smeared charges, Thole polarizable sites, an optional MM force field (MmTopology). See QM/MM. The CLI [qmmm] section covers fixed point charges only.
QM/MM optimizationrun_optimize_qmmmSame four methods. move_mm = "none"/"all"/("within", r)/("residues", [...]). Moving MM atoms requires mm_topology.
Constrained DFTrun_cdft, CdftConstraintUHF, or UKS when functional names a libxc functional other than "HF" (None and "HF", any case, give UHF). Constrained UKS/PBE matches NWChem 7.2.2 (see anchors); UHF-cDFT state selection is compared against NWChem's DFT module with Hartree–Fock exchange (see anchors); UHF energies along a target scan and UHF couplings have no external comparison. Fragment target is a Becke electron population ("charge": Nα + Nβ; "spin": Nα − Nβ), not a net charge. Raises if the λ loop does not converge. Not graded; see Constrained DFT.
cDFT electron-transfer couplingcdft_couplingWu–Van Voorhis H_ab between two run_cdft states, each with one converged "charge" constraint, on the same geometry, basis, occupations and Hamiltonian. Not graded.
Point charges / uniform fieldpoint_charges=, external_field= on run_rhf/run_uhf/run_rohf/run_dft, run_optimize, run_frequencies, run_saddle, run_irc, run_pdep_rpaBohr and atomic units. run_rhf also takes smeared_charges=. The MP2/CC drivers take no external-potential arguments. The CLI equivalent is [external_potential], where a width makes a charge smeared.
D3(BJ)run_dft(dispersion="d3bj"), run_frequencies(xc=..., dispersion="d3bj"), d3bj_energyAdditive, with parameters fitted per functional. run_frequencies takes it on RKS, UKS and ROKS.
MBD@rsSCSrun_dft(dispersion="mbd"), run_frequencies(xc=..., dispersion="mbd"), mbd_rsscs_energyrun_dft adds it to total_energy from Hirshfeld volume ratios of the converged density (DftResult.volume_ratios), with β published for PBE, PBE0, HSE06. With with_gradient=True it adds the analytic gradient: the exact analytic gradient, including the orbital relaxation of the Hirshfeld volumes (Z-vector; LDA, GGA and hybrid-GGA functionals without VV10). mbd_rsscs_energy is the standalone energy from caller-supplied ratios.
Chargesmulliken_charges, lowdin_charges, hirshfeld_charges, chelpg_charges, resp_chargesTake an RhfResult or DftResult. Mulliken, Löwdin, CHELPG and RESP are documented closed-shell only. hirshfeld_charges uses the CLI's free-atom SCF proatoms, solved with the result's SCF settings; proatom="slater" selects the single-Slater proatom. RESP is a single-stage restrained fit, not multi-conformer RESP.
Electrostatic potentialesp_at_atoms, esp_at_pointsEvaluated exactly from the density. esp_at_points takes (N, 3) points in Bohr.
Polarizability / momentshirshfeld_polarizability, orbital_moments, density_second_moment—
ω tuningtune_omegaRange-separation ω for a named functional. With continuation=True (off by default) each evaluation after the first is seeded from the nearest already-evaluated ω; the first uses the default guess. The cation's spin-population asymmetry is compared with that same neighbour (branch_tol, default 1e-7); a mismatch sets branch_warning and the per-evaluation branch_changed. ⟨S²⟩ is reported but not compared, because its smooth ω-drift exceeds the signal. With check_cation_stability (default on) every cation also gets the UKS internal-stability analysis: each evaluation carries cation_lambda_min (the orbital Hessian's lowest eigenvalue) and cation_stability, and a tuned ω whose cation is an internal saddle is an error naming ω and λ_min. A functional whose Hessian cannot be built (VV10, so ωB97X-V) is reported not_analysed per evaluation with a stability_warning; that is never read as stable.
Conformer statisticsboltzmann_weights, weighted_stats*, ConformerEnsemble—
Integralscompute_eri3, compute_eri3_mo, compute_metric_2c, boys_localize, shell_infoLow-level access.

Polarizable (Thole) embedding is Python and Rust only. IEF-PCM, UHF stability descent and terfc-attenuated MP2 are reachable from both: see [pcm], [scf] stability_descent and [mp2] att_operator in the Input reference.

Anchors

Where numbers are checked, they are checked against external references or exact limits, not against ferric's own earlier output. "Stated agreement" is the measured difference recorded in the repository; "test tolerance" is what the pinning test asserts, which is often looser. Proof links to the test file that asserts the row, and, where one exists, the script that generated its reference data; a row with nothing to link is not graded Proven. Proof files named validation_*.rs are #[ignore]d, so an ordinary cargo test skips them; they run in the weekly validation / validation-* CI jobs and on demand (cargo nextest run --run-ignored only -E 'binary(/^validation_/)').

CapabilitySystem / basisReferenceStated agreementTest toleranceProof
Bundled basis sets: every registered orbital, RI and JK set loads to the published contractions45 orbital-set RHF cases (Pople 6D/5D per the Gaussian convention, Dunning, def2, pcseg, -PP with ECPs), 26 RI-MP2 aux cases, 6 DF-HF JK cases (incl. aug-cc-pV{D,T,Q}Z-JKFIT); small molecules (H2O, HCl, CH3Cl, HBr, Pd, …)PySCF 2.13.1 with each basis taken from BSE 0.12 by name (never ferric's files); aug-JKFIT from Psi4, digit-identical to PySCF's copy1.8e-12 Ha (RHF); 2.4e-10 Ha (RI-MP2, PP-RIFIT on Pd; others ≤ 5e-11); 7.3e-13 Ha (DF-HF)1e-8 Havalidation_basis_coverage.rs (full sweep), basis_coverage_vs_pyscf.rs (per-commit subset), gen_pyscf_basis_coverage_refs.py
UHF and ROHF energies, stability-checkedHO2, NO2, CH2 (triplet), allyl / 6-31G, def2-SVPPySCF UHF/ROHF + stability()4.0e-12 Ha (energy); 3e-7 (⟨S²⟩)1e-10 Ha; 1e-6validation_open_shell_scf.rs, gen_uhf_rohf.py
SCF stability: lowest orbital-Hessian eigenvalues and verdict (UHF internal, UKS internal, RHF internal, RHF→UHF external). RhfConfig::check_stability on an RHF/HF run reports both the internal (singlet) verdict on ScfResult::stability and the external (RHF→UHF triplet, rhf_external_stability) verdict on ScfResult::stability_external; the external operator is also checked against the triplet block of ferric's UHF Hessian at the RHF point, an independent constructionN2⁺, OH (UHF), water at r(OH) = 0.9572 and 2.0 Å (RHF), NH2 (UKS/PBE) / 6-31G; H₂ at 2.0 Å / STO-3G (fast tier)PySCF dense gen_g_hop_uhf/gen_g_hop_rhf/hop_rhf2uhf Hessians + stability()eigenvalues ≤ 4.5e-10 Ha; energies ≤ 7.1e-12 Ha; the dedicated triplet operator matches the UHF-at-RHF triplet block to ≤ 2.3e-13 Ha (two independent constructions); stable/unstable verdicts match PySCF, including water at 2.0 Å where internal is STABLE (+1.97e-2) and external UNSTABLE (−3.07e-1); OH (an exact zero mode) is reported MARGINAL by ferric where PySCF reports stable. KS references are refused for the external channel (no triplet XC kernel f_αα − f_αβ)1e-8 Ha; 1e-10 Ha; 2e-12 Havalidation_scf_stability.rs, scf_stability_external.rs, validation_rsh_stability.rs, gen_scf_stability.py
SCF convergence ladder with RHF internal stability descent (solve_rhf_ladder, check_stability + scf_stability_descent): converged energy, internal (singlet) and external (RHF→UHF) Hessian eigenvaluesN2 at 1.60 Å, CuCN, Cr(CO)6 / def2-SVP (Cu, Cr from Basis Set Exchange)PySCF RHF .newton() from four guesses; dense singlet and triplet orbital Hessians from MO integrals, checked against PySCF's own operatorsenergy ≤ 8.8e-10 Ha; eigenvalues ≤ 1.3e-9 Ha; the plain-DIIS rung lands on the N2 saddle 1.906e-2 Ha higher; the UHF-Hessian cross-construction of the external eigenvalue runs on N2 and CuCN only, and Cr(CO)6 asserts the dedicated triplet operator's result (its own weekly CI job, validation-cr-co6)1e-8 Ha; 1e-8 Havalidation_scf_ladder.rs, rhf_stability_descent.rs, gen_scf_ladder.py
RHF and UHF with def2 ECPs: energies and analytic gradientsHI, CH3I, RbH, I atom (UHF) / def2-SVP, def2-TZVP; SnH4 / def2-TZVP; HBr (all-electron control)PySCF RHF/UHF + analytic gradient, and ORCA RHF (NoRI), both fed ferric's basis and ECP≤ 2.9e-8 Ha (energy, both codes); ≤ 2.3e-8 Ha/Bohr (gradient); all-electron HBr 1e-112.5e-7 Ha; 1e-7 Ha/Bohrvalidation_ecp.rs, gen_ecp.py, gen_ecp_orca.py
RHF, UHF, RKS, UKS, ROKS and RI-MP2 nuclear gradients with def2 ECPs: full gradient, the ECP term alone (HF and KS; for RI-MP2 the ECP term is tested through the full gradient and a control that the HF-density ECP term misses), translation invariance, 5-point finite difference of ferric's own energyHF: HI, CH3I / def2-SVP, def2-TZVP; SnH4 / def2-TZVP; CH2I (UHF doublet) / def2-SVP, def2-TZVP; HBr (all-electron control). KS/PBE: CH3I (RKS), CH2I (UKS, ROKS) / def2-SVP. RI-MP2: CH3I / def2-SVP, def2-svp-rifitHF: PySCF analytic gradient with its ECP term separated (checked against a fixed-density finite difference), and ORCA EnGrad (NoRI), both fed ferric's basis and ECP. KS: PySCF with exact J, the matched (75,110) Becke grid and grid_response=True (analytic for RKS/UKS, 5-point finite difference of the ROKS energy). RI-MP2: 5-point finite difference of the PySCF DF-MP2 energy, its ECP term from the relaxed-energy derivative with respect to a λ dV_ECP/dR hcore perturbation, and ORCA RI-MP2 EnGradHF: gradient ≤ 1.3e-7 Ha/Bohr vs PySCF and ≤ 2.5e-7 vs ORCA; the ECP term alone ≤ 1.3e-7; own 5-point FD ≤ 1.9e-7; energy ≤ 2.9e-8 Ha. KS: energy ≤ 2.0e-8 Ha, gradient ≤ 1.4e-8 Ha/Bohr, the ECP term alone ≤ 1.1e-8, own FD ≤ 4.3e-8. RI-MP2: E_corr 7.4e-10 Ha; gradient 7.7e-9 Ha/Bohr vs the PySCF finite difference and 4.3e-8 vs ORCAHF: 5e-7 Ha/Bohr (PySCF), 1e-6 (ORCA), 6e-7 (FD). KS: 2e-7 (PySCF), 6e-7 (FD). RI-MP2: 1e-7 (PySCF FD), 5e-7 (ORCA)validation_ecp_gradient.rs, validation_ecp_rimp2_gradient.rs, gen_ecp_gradient.py
ESP at the nucleiH2O, CH3OH (RHF), HO2 (UHF) / cc-pVDZ, def2-SVPPySCF int1e_rinv on PySCF's converged density, fed to ferric in ferric's AO order (AO order checked through the overlap matrix)4.8e-14 a.u. same density; 4.6e-9 own SCF5e-13 a.u.; 5e-8 a.u.validation_density_properties.rs, gen_properties.py
Electric field at the nucleiH2O, CH3OH, HO2 / cc-pVDZ, def2-SVPPySCF int1e_iprinv, same density (checked against a finite difference of the ESP)1.5e-13 a.u. same density; 2.7e-9 own SCF1e-12 a.u.; 3e-8 a.u.″
Becke effective volumesH2O, CH3OH, HO2 / cc-pVDZ, def2-SVP; free atoms Z = 1–18 (UKS-PBE; B, C, O, F, Al, Si, S, Cl as the fractional-occupation ensemble) / aug-cc-pVDZnumpy on ferric's grid rebuilt from PySCF's radial, Lebedev and AO values, with ferric's Becke partition; free atoms vs PySCF UKS1.4e-15 rel. same density; 7.1e-9 (molecules), 3.5e-8 (atoms) own SCF; Li, Be, Na, Al 1.5e-6 from ferric's XC density floor (ρ ≤ 1e-10), which the reference does not apply; free-atom energies 3.1e-12 Ha1e-13; 5e-8; 2e-6 (Li, Be, Na, Al)″
Hirshfeld charges (hirshfeld_charges) with the same-basis free-atom SCF proatom (what the CLI and ferric.hirshfeld_charges pass by default) and with the single-Slater proatom (proatom="slater"); free-atom proatom tables (spherically_averaged_proatom)H2O, CO, CH3OH (RHF) / cc-pVDZ, def2-SVP; free H, C, O (UHF) and He (RHF) atoms; HeH promolecule (zero-charge limit)numpy on ferric's grid rebuilt from PySCF's radial, Lebedev and AO values, with PySCF's converged density and PySCF free-atom proatoms tabulated and interpolated as ferric does (scipy cubic spline of ln ρ, an independent build of the interpolant); the same charges on a dense (200, 590) grid and on PySCF's own level-9 grid (agreeing to ≤2.8e-7 e)1.9e-13 e same density; 2.6e-9 e own SCF and own free atoms; ferric's default grid ≤2.0e-4 e from the dense-grid value; the 0.05 Bohr proatom table ≤2.9e-7 e from a 0.005 Bohr one; promolecule 8.7e-6 e1e-10 e; 1e-6 e; 1e-3 e; 5e-5 evalidation_hirshfeld.rs, gen_hirshfeld.py, validation_properties_hirshfeld.rs
Hirshfeld effective volumes (atomic_effective_volumes_hirshfeld, the TS C6 volumes), on the atom-centred Becke–Lebedev grid of hirshfeld_volume_grid_config (75 radial × 590 angular — the volume integrand carries an r³ factor and needs a higher Lebedev order than the XC grid's 110); and atomic_effective_volumes_hirshfeld_on_grid on the 0.20 Bohr lattice, which MBD@rsSCS integrates onH2O, CO, CH3OH / cc-pVDZ, def2-SVP (SCF proatom); free H, C, O atoms (no proatom, as the CLI's free-atom denominator)numpy on a dense (200, 590) Becke–Lebedev grid, same density and proatoms; the free-atom weight-one moment ∫ρ r³ against the same grid is the exactness anchor (one atom ⇒ Hirshfeld weight 1); plus numpy on ferric's lattice rebuilt point for point for the lattice entry point1.1e-7 rel. vs the dense grid, same density and own SCF alike; ∫ρ within 2.0e-7 e of N_e on the same grid; CH3OH's two mirror-image methyl H equal to 2.9e-14 rel.; free-atom weight-one moment 8.1e-13 rel. (the 1e-12 Hirshfeld weight floor then removes 1.2e-3 of free H's volume, 3.4e-10 of C's); lattice entry point 3.2e-13 rel. same density1e-6; 2e-6 e; 1e-10; 1e-11; 1e-11″
TS C6 and MBD@TS given the same volume ratios: TS α_eff, ω, pair and molecular C6 (ts_atom_params, ts_dynamic_polarizability, casimir_polder_c6); full-range SCS screened α(iω) and C6 (mbd_screen, mbd_dynamic_polarizability), and the plain Gaussian-damped coupled-oscillator energy (coupled_qho_energy_plain_gg), which checks the oscillator algebra and is not a dispersion energyH2O, CO, CH3OH / cc-pVDZ, def2-SVP; CH4, benzene / cc-pVDZ (Hirshfeld volume ratios); ferric's and pymbd's frequency gridspymbd 0.15.0 / libmbd: free-atom table checked equal for every element used; MBD screening as libmbd scs and energy as libmbd plain screening with Gaussian damping (dip,gg), cross-checked against a numpy build and mpmath dimer closed formsTS ≤ 7.5e-16 rel.; MBD 8.1e-15 against the same A&S erf ferric uses, 5.1e-7 against an exact erf1e-12; 1e-11; 1e-6validation_ts_mbd.rs, gen_ts_mbd.py
MBD@rsSCS energy (mbd_rsscs_energy, mbd_rsscs_energy_from_params): range-separated SCS screening (short-range (1−f)·T_GG), screened α₀, C6, ω and R_vdW, and the long-range coupled-oscillator energy with f·T_bare; free-atom R_vdW table (Z = 1–54); frequency gridH2O, CO, CH3OH / cc-pVDZ, def2-SVP; CH4, benzene / cc-pVDZ (Hirshfeld volume ratios); β = 0.83 (PBE) and 0.85 (PBE0/HSE06), a = 6pymbd 0.15.0 (screening + mbd_energy in Python) and libmbd 0.15.0 (Fortran, variant='rsscs') given identical inputs, which agree with each other to < 1e-10; R_vdW table read back from pymbd vdw_params; grid vs pymbd freq_grid(15)E ≤ 9.5e-12 rel.; screened α₀, C6, ω, R_vdW ≤ 1.5e-14 rel.; grid 3.8e-141e-10; 1e-12; 1e-12validation_mbd_rsscs.rs, gen_ts_mbd.py
MBD@rsSCS nuclear gradient, exact (mbd_rsscs_gradient; on an SCF mbd_rsscs_for_scf, used by [dft] dispersion = "mbd" with task = "optimize" and by run_dft(with_gradient=True)): explicit term at fixed volume ratios; Hirshfeld-volume term with the occupied orbitals held fixed (AOs and proatoms follow the atoms, orthonormality term −½ Tr[V D Sˣ D], centroid-anchored lattice response); orbital relaxation from the KS Z-vector (ferric_scf::zvector_ks: closed-shell for RKS; coupled α/β for UKS, with orthonormality term −Σ_σ Tr[V D_σ Sˣ D_σ]; three-block closed/open/virtual for ROKS, with orthonormality term −Tr[Sˣ W_Q], W_Q = D_α V D_α + P_c V P_c + ½ (P_c V P_o + P_o V P_c))explicit: the 8 systems × 2 β above; SCF terms: H2O / STO-3G (HF and PBE), H2O / 6-31G (PBE, PBE0, HSE06, PBE + RI-J), NH3 / 6-31G (PBE); UKS: OH / STO-3G (PBE), NH2, OH (doublets) and O2 (triplet) / 6-31G (PBE, PBE0, HSE06), OH / 6-31G (PBE + RI-J, PBE0 + RI-JK); anchor: H2O / STO-3G RKS run through the UKS path (PBE, PBE0, HSE06); ROKS: HCO (doublet) and CH2 (triplet) / STO-3G (PBE), HCO, NH2 (doublets), CH2 and O2 (triplets) / 6-31G (PBE, PBE0, HSE06), HCO / 6-31G (PBE + RI-J); anchor: H2O / STO-3G RKS run through the ROKS path (PBE, PBE0, HSE06)explicit vs libmbd 0.15.0 force=True (checked against libmbd's own finite differences to ≤ 4.2e-11) and vs central FD in every position and every α₀, C6, R_vdW input; volume term vs central FD of the volumes on a fixed lattice at fixed D; the unrelaxed gradient vs central FD of the pipeline with the reference occupied orbitals re-orthonormalized at each displaced geometry; the exact gradient vs central FD of the full SCF + MBD pipeline, and its relaxation term vs the difference of the two FDsexplicit 6.2e-16 abs vs libmbd; parameter derivatives ≤ 1.3e-6 rel. (FD); volume term 2.5e-8 abs (Slater proatom), 6.5e-9 (tabulated proatom), both at h = 1e-4; unrelaxed 3.4e-9 (H2O/STO-3G HF); exact 8.0e-10 (H2O/STO-3G PBE), ≤ 3.5e-9 (H2O/6-31G, all four), 2.0e-10 (NH3/6-31G) Hartree/Bohr at h = 1e-3; relaxation term (1.0e-5 H2O, 1.6e-6 NH3) vs FD ≤ 3.5e-9; UKS (h = 3e-5): unrelaxed 3.9e-11 and exact 2.9e-11 (OH/STO-3G, h = 1e-3), exact ≤ 1.9e-9 (6-31G, exact J/K), 6.0e-9 (RI-J), 5.0e-9 (RI-JK), relaxation term (2.7e-6–1.0e-5) vs FD ≤ 6.0e-9; the UKS path on a closed shell reproduces the RKS gradient to 3.0e-14; ROKS (h = 1e-3, re-orthonormalizing closed and open orbitals as one set for the unrelaxed FD): unrelaxed ≤ 2.2e-11 against a closed–open cross term of 7.1e-8–3.2e-7, exact ≤ 2.1e-11 (STO-3G), ≤ 2.1e-11 (6-31G, PBE and PBE0), ≤ 4.5e-10 (HSE06), 7.5e-9 (RI-J), relaxation term (2.7e-6–9.8e-6) vs FD ≤ 7.5e-9, every displaced SCF on the reference state (max|ΔD|/h ≤ 0.39 per Bohr); the ROKS path on a closed shell reproduces the RKS gradient to 2.7e-141e-14; 1e-5 rel.; 1e-6 rel. (Slater), 5e-8 rel. (tabulated); 5e-8; 5e-9; UKS 5e-10, anchor 1e-12; ROKS 5e-10, anchor 1e-12validation_mbd_rsscs.rs, mbd_scf_gradient.rs, mbd_scf_gradient_uks.rs, mbd_scf_gradient_roks.rs, unit tests in dispersion/mbd_rsscs.rs
MBD@rsSCS exact gradient on ²Π radicals (UKS and ROKS Z-vector with the axis-rotation null mode projected out, zvector_ks::NullModeDiagnostics)OH / STO-3G (UKS PBE, PBE0; ROKS PBE0), OH / 6-31G (UKS PBE), NO / STO-3G and 6-31G (UKS PBE), CH / 6-31G (ROKS PBE); null-mode identification on OH, NO / STO-3G and OH / cc-pVDZcentral FD of the full SCF + MBD pipeline (h = 1e-3); the softest eigenvector of the dense orbital Hessian, an independent construction of the modeexact gradient 7.1e-12–5.4e-11 Hartree/Bohr against relaxation terms of 2.2e-7–1.0e-5; the mode matches the softest eigenvector to |cos| > 0.9999, Rayleigh quotients −3.5e-3 to +9.9e-3 against ≥ 0.57 for every other direction; a non-linear radical (NH2) is bit-identical to the unprojected solver5e-10; 0.9999; 0.05mbd_scf_gradient_degenerate.rs, unit tests in zvector_ks.rs
Harmonic frequencies with dispersion (harmonic_frequencies_with_scf_correction; CLI task = "frequencies" + [dft] dispersion, Python run_frequencies(dispersion=...)): central differences of the KS + dispersion analytic gradient, each from the SCF converged at that displaced geometryH2O / STO-3G, PBE (D3(BJ), MBD@rsSCS); H2 / STO-3G HF with a synthetic harmonic-spring correctionD3: the dispersion Hessian vs 4-point second differences of the D3(BJ) energy, and the frequencies vs those of the KS Hessian plus that independent D3 Hessian; MBD: v·H·v along three fixed directions vs second differences of the full SCF + MBD energy; spring: the closed-form spring Hessian; a no-op correction reproduces the plain FD Hessian bit for bitD3 5.3e-9 Hartree/Bohr² (largest element 1.9e-5), frequencies 9.8e-6 cm⁻¹; MBD 1.5e-6 (0.8% of 2.0e-4, limited by noise in the energy differences; without the orbital-relaxation term 3.2e-5); spring 6.6e-8 at a 1e-3 Bohr step (truncation, 25× smaller than at 5e-3)2e-8; 5e-5 cm⁻¹; 3e-6; 2e-7dispersion_frequencies.rs
Harmonic frequencies with dispersion (open shell): harmonic_frequencies_with_scf_correction on UKS (reference = Uhf) and ROKS (reference = Rohf); CLI uhf/rohf/ksdft + [dft] functional + [dft] dispersion with task = "frequencies", Python run_frequencies(reference="uhf"|"rohf", dispersion=...). Central differences of the KS + dispersion gradient, each from the UKS/ROKS SCF converged at that displaced geometry; MBD@rsSCS with the UKS or ROKS Z-vectorPBE / STO-3G, bent geometries: NH2 (doublet, UKS), HCO (doublet, ROKS; in-plane SOMO), CH2 (triplet, UKS and ROKS). OH is excluded (degenerate SOMO)D3: the dispersion Hessian vs 4-point second differences of the D3(BJ) energy, the frequencies vs those of the KS Hessian plus that independent D3 Hessian, and the translational and rotational sum rules of the D3 Hessian; MBD: v·H·v along three fixed directions vs 5-point second differences (h = 0.08 Bohr) of the full SCF + MBD energy, with the unrelaxed gradient (no Z-vector) as negative control; a no-op correction reproduces the plain UKS / ROKS FD Hessian bit for bit, and the correction sees an SCF of the requested spinD3 3.7e-9 (NH2) and 6.5e-9 (HCO) Hartree/Bohr² (largest elements 1.7e-5, 2.9e-5), frequencies 9.4e-6 and 3.0e-5 cm⁻¹; sum rules: translation ≤ 1.1e-11, rotation ≤ 3.0e-9 against a gradient term of 1.9e-6–1.1e-5; MBD ≤ 4.8e-8 (curvatures 6.1e-5–2.1e-4) vs 8.3e-7–1.6e-5 without the orbital-relaxation term; full corrected Hessian translational sum ≤ 1.3e-6 (KS grid noise)2e-8; 5e-5 cm⁻¹; 1e-10, 1e-8; 2e-7; 1e-5dispersion_frequencies_open_shell.rs, validation_dispersion_frequencies_open_shell.rs
D3(BJ) dispersion energy and analytic gradient (ferric_d3::d3bj_energy, d3bj_gradient), two-body and with the Axilrod–Teller–Muto three-body term (s9 = 1: zero damping, rs9 = 4/3, exponent 16, CN-interpolated C6)Two-body: 24 system/functional cases. With ATM: water, methane, Ar2, water dimer, Br2, benzene, Ar3, CF2ONH, CHBr3 × PBE, PBE0, B3LYP (27 cases; the three-body term is nonzero on the 21 with three or more atoms)simple-dftd3 1.6.0 (dftd3 Python package, an independent Fortran implementation) with the parameters passed explicitly; central FD of ferric's energy; a sympy proof of the CN chain rule and of the ATM gradienttwo-body energy ≤ 1.5e-13 Ha; with ATM: energy ≤ 1.2e-13 Ha, gradient ≤ 5.5e-13 Ha/Bohr, the three-body part alone ≤ 2.6e-15 Ha and ≤ 5.9e-16 Ha/Bohr; analytic vs FD ≤ 3.0e-11 Ha/Bohr (CF2ONH, CN spread 1.75); E linear in s9 to 1e-15, a dimer bit-identical at s9 = 0 and 11e-12 Ha (energy); 1e-11 Ha/Bohr (gradient); 1e-12 Ha/Bohr (three-body gradient); FD 1e-7 Ha/Bohrvs_reference_dftd3.rs, vs_reference_dftd3_atm.rs, gradient_vs_fd.rs, gen_d3_atm_reference.py, d3_cn_chain_rule_gradient_sympy.py, d3_atm_gradient_sympy.py
Static α (direct RPA with a density-fitted Coulomb kernel, no exchange; not CPHF)H2O / aug-cc-pVDZ; CH3OH / cc-pVDZnumpy linear solve on PySCF int3c2e with the same aux basis4.8e-14 rel. same orbitals; 5.3e-10 rel. own SCF5e-13; 5e-9validation_static_alpha.rs, gen_properties.py
Dynamic α(iω) and Casimir–Polder C6 (direct RPA, density-fitted Coulomb kernel, no exchange), molecular and Becke per-atom (molecular_dynamic_polarizability, pdep_polarizability_becke_dynamic, casimir_polder_c6); per-atom α is the Krishtal–Senet–Van Alsenoy intrinsic polarizability (atom-centred Becke dipole on the bra, analytic molecular dipole on the ket; JCP 125, 034312 (2006)) and the charge-transfer remainder α_CT = α_mol − Σ_A α^A (charge_transfer_remainder[_dynamic]); ω = 0 against the static α, molecular and per-atomH2O, N2 / aug-cc-pVDZ (aug-cc-pvdz-rifit)numpy sum over Casida excitations on PySCF int3c2e with the same aux basis, at ferric's 20 Gauss–Legendre nodes (rebuilt with leggauss); per-atom and α_CT on the copy of ferric's Becke grid, α_CT also as the independent charge-flow form 4 (Σ_A R_A q^A)ᵀ R μα(iω) 4.9e-14 rel. same orbitals; C6 6.2e-14; per-atom 3.4e-14; 5.3e-10 own SCF; α_CT,iso(0) is 27.0% (H2O) and 22.7% (N2) of α_iso; α_CT vs charge-flow form 1.1e-4 / 2.0e-5 (grid error of the lab dipole)1e-11; 1e-11; 1e-10; 5e-9; α_CT 1e-10, charge-flow 1e-3validation_pdep_c6.rs, gen_pdep_c6.py
NPZ export ([rpa] export_npz)water / STO-3G, CLI pdep-rpanumpy.load of the CLI's file against the Python bindingsexact (keys, shapes, dtypes, C order, geometry); per-atom and molecular α origin-independent to 3.4e-13 rel.; alpha_ct = alpha_tensor − Σ_A alpha_atomicexact; 1e-11 rel. under translation; 1e-13test_validation_npz_export.py
NPZ export against PySCF: all 30 keys for the default knobs plus the surface ESP, including the C6 block and the PDEP eigenpotentials; each knob turned off removes exactly its keyswater / cc-pVDZ, CLI pdep-rpa with exact J/Knumpy.load of the CLI's file against PySCF integrals contracted with the exported density and orbitals, and against PySCF RHF; α against the numpy dRPA-RI solve; PDEP eigenpotentials against PySCF's generalized dielectric eigenproblem; TS C6 recomputed from the exported α(iω) and the Tkatchenko–Scheffler free-atom tableintegral level ≤2.8e-14; density/orbital-energy/property chain ≤5.9e-10; α 9.2e-11 rel.; PDEP metric 4.3e-12, projector 2.6e-7; C6 recomputed exactly5e-13; 1e-8 (density), 3e-9; 1e-9; 5e-11, 1e-6; 1e-12validation_npz.rs, gen_npz.py
KS-DFT energies: open-shell UKS and second-row closed shellUKS: NH2, CH3, HO2, O2 (triplet) × PBE, B3LYP, ωB97X-V / 6-31G, def2-SVP; RKS: H2S, HCl, SiH4 × PBE, B3LYP, HSE06 / def2-SVP, def2-TZVPPySCF UKS/RKS + stability(), same grid and density fitting; for UKS ωB97X-V both codes use exact J and range-separated DF-K, whose fitting metrics differ, which sets that functional's larger bar; for RKS HSE06 both use exact J and short-range DF-K in the attenuated metricenergy ≤3.1e-12 Ha (PBE, B3LYP), 4.2e-12 Ha (HSE06), 6e-6 Ha (ωB97X-V); ⟨S²⟩ ≤4.6e-9 (PBE, B3LYP), 1.3e-7 (ωB97X-V). The VV10 potential Vnl equals the derivative of Enl: FD(Exc) − Tr[V X] ≤ 8e-11 for a random symmetric X on ωB97X-V (NH2 / 6-31G, closed and polarized; test_xc_blocks.py), and the low-gradient σ derivative matches FD to 5.3e-7 relative (unit test in vv10.rs, bar 1e-5). The ωB97X-V static polarizability αzz of water / 6-31G (RI-JK) from the CPKS solve matches the finite-field SCF dipole to 1.5e-10 and the finite-field −d²E/dF² to 8.7e-10 relative (test_vv10_polarizability_matches_the_finite_field_dipole in test_response_blocks.py; bars 1e-8 and 2e-8); with the σ gate on ∂ω₀/∂σ restored the two routes miss by 1.3e-5 and 5.1e-5.energy 1e-10 Ha (PBE, B3LYP, HSE06), 2e-5 Ha (ωB97X-V); ⟨S²⟩ 5e-8, 1e-6validation_ks_energies.rs, gen_ks_energies.py
KS-DFT energies: ROKS (rohf with [dft] functional)NH2, CH3, HO2, OH (degenerate π hole) × PBE, B3LYP / 6-31G, def2-SVPPySCF ROKS, same grid and density fitting (RI-J; RI-K for B3LYP), six starts each polished by second-order SCF and checked with stability(); PySCF's own ROKS does not converge on OH without that polish, and its converged OH points spread by up to 1.3e-6 Ha (π-hole orientation on the grid)8.0e-13 Ha; OH 8.0e-7 Ha1e-11 Ha; OH 5e-6 Havalidation_roks_energies.rs, gen_roks_energies.py
Meta-GGA energies (SCAN, r2SCAN)RKS: H2O, NH3, H2S, CH4 / def2-SVP, def2-TZVP; RI-J (default) and exact J; UKS: NH2 / def2-SVP, def2-TZVPPySCF RKS/UKS + stability(), same grid, density fitting and XC density floorRKS energy ≤ 4.0e-12 Ha, UKS ≤ 2.4e-13 Ha; frontier orbitals ≤ 1.1e-8 Ha; ⟨S²⟩ ≤ 5.2e-10energy 5e-11 Ha; orbitals 1e-7 Ha; ⟨S²⟩ 5e-9validation_mgga_energies.rs, gen_mgga_energies.py
Meta-GGA TPSS (molecular): energies and closed-shell gradientRKS: H2O, CH4 / cc-pVDZ; UKS: NH2 / cc-pVDZ; RI-J (def2-universal-jkfit); gradient: H2O / STO-3GPySCF RKS/UKS (TPSS = MGGA_X_TPSS + MGGA_C_TPSS), flat (75,110) Becke-1988 grid, same density fitting; gradient: central FD of ferric's own energyenergy ≤ 2.1e-13 Ha (H2O 5.7e-14, CH4 1.6e-13, NH2 UKS 2.1e-13); gradient vs FD ≤ 1.4e-7 Ha/Bohr1e-4 Ha (the dft_scan.rs bar shared with SCAN / r2SCAN); 3e-4 Ha/Bohr. Limits: no PySCF gradient pin, no open-shell gradient, one basis per energy casedft_scan.rs, dft_gradient_mgga.rs, gen_pyscf_mgga_refs.py
SCF stability for a range-separated hybrid: the lowest UKS-internal orbital-Hessian eigenvalue and the stable/unstable verdict, with the exchange response built as c_SR·δK[erfc(ω)] + c_LR·δK[erf(ω)] from the same density-fitted SR/LR pair the converged Fock uses, and the f_xc kernel built at the same overridden ω. RhfConfig::check_stability refuses ωB97X-V itself (StabilitySkip::Vv10Kernel: nothing in the workspace has a VV10 response kernel), so this row is measured through the #[doc(hidden)] rsh_uks_lowest_hessian_eigenvalue entry point, which accepts that omission to compare against a reference making the same oneN2⁺ / def2-SVP, ωB97X-V with ω overridden to 0.40 and 0.60 Bohr⁻¹, bracketing the 0.53–0.56 instability onset; OH / cc-pVDZ for the fast-tier kernel and finite-difference checksPySCF dense newton_ah.gen_g_hop_uhf on a dft.UKS with mf.omega overridden, exact J and the matched (75,110) / VV10 (50,50) grids, the cation converged from the default guess and not stability-followed (past the onset the symmetric branch is a saddle, which is the point); PySCF's KS response omits the VV10 second derivative and so does ferric's, so the comparison is like-for-like on that omissionλ_min ≤ 1.9e-7 Ha and SCF energy ≤ 1.7e-8 Ha at both ω; verdicts match (STABLE at 0.40, UNSTABLE at 0.60); the plain-Coulomb exchange response (one Coulomb-kernel δK scaled by c_SR) misses the same spectrum by ≥ 2.9e-1 Ha. Fast tier: the density-fitted SR/LR response agrees with c_SR·K[erfc(ω)] + c_LR·K[erf(ω)] from four-centre integrals to 7.7e-4 relative, while kernels swapped, c_SR for both, c_LR dropped and ω doubled each sit ≥ 0.42 away; the analytic matvec matches a finite difference of the orbital gradient at ω = 0.40 to 5.3e-12 (falling 16× for a 4× smaller step); erfc(ω) + erf(ω) at equal coefficients reproduces the global-hybrid exchange to 8.2e-15 relative in four-centre integrals; at ω = 0 the matvec is bit-identical2e-6 Ha (~10× the measured max); 1e-7 Ha; 8e-3 rel.; 1e-6 rel.; 1e-12 rel.validation_rsh_stability.rs, rsh_orbital_hessian.rs, gen_scf_stability.py
RSH ω tuning (tune_omega): the objective J(ω) = ε_HOMO(N) + E(N−1) − E(N) (signed; the tuner minimizes |J|) and the tuned ω*H2O, NH3 / def2-SVP, ωB97X-V: J at ω = 0.2, 0.3, 0.4, 0.5, 0.6 Bohr⁻¹ and ω*; N2 / def2-SVP: J at ω = 0.2–0.5 only, because the N2⁺ cation turns UKS-unstable between ω = 0.53 and 0.56, below J's root. For ωB97X-V that onset cannot be flagged by tune_omega (VV10: no response kernel, so every evaluation is not_analysed and the result warns). On that symmetric branch ferric's J matches the PySCF probe to 7.4e-8 Ha (ω = 0.53) and 4.7e-7 Ha (ω = 0.56) and its ⟨S²⟩ to 3.0e-7, and J, ⟨S²⟩ and the cation spin-population asymmetry are all smooth across the onset: a lower cation state exists past it (reached when seeded toward it: 8.9e-5 Ha below the symmetric state at ω = 0.56, 4.6e-4 Ha at 0.60) but the tuner does not find it, and no energy, ⟨S²⟩ or population quantity flags it, because what changes there is a curvature; only the orbital Hessian's lowest eigenvalue does (next paragraph). continuation=True keeps ω* bit-identical on H2O and NH3; on H2O its J differs from the independent solves by SCF stopping noise in ε_HOMO that tracks density_conv: 1.7e-7 Ha at 1e-7, 2.6e-10 Ha at 1e-9PySCF RKS neutral and stability-checked doublet UKS cation with mf.omega overridden, same grid, exact J on both states and range-separated DF-K; ω* from brentq on J. Both states use exact J, as tune_omega does by default. Cation stability (non-VV10 ωB97X, def2-SVP, mf.omega overridden, default guess, dense gen_g_hop_uhf λ_min): N2⁺ λ_min = +3.06e-2, +2.01e-2, +1.23e-2, +2.2e-3 at ω = 0.30, 0.40, 0.45, 0.50 and −6.9e-3, −1.50e-2, −3.83e-2 at 0.55, 0.60, 0.80, so the symmetric cation is a saddle from between 0.50 and 0.55 on; H2O⁺ and NH3⁺ are ≥ +7.8e-2 at ω = 0.3, 0.6, 1.0. A tune_omega bracket past the onset raises an error naming ω and λ_min; a bracket below it returns with no warningε_HOMO ≤6.7e-6 Ha (N2 4.8e-7); IP ≤8.8e-7 Ha; J ≤6.3e-6 Ha; ω* ≤2.8e-5 Bohr⁻¹; cation λ_min ≤2.3e-8 Ha/rad² (N2 1.8e-9), verdict sign matches PySCF at all 13 points2e-5 Ha; 5e-6 Ha; 2e-5 Ha; 1e-4 Bohr⁻¹; 3e-7 Ha/rad²validation_rsh_omega.rs, omega_tuning_cation_branch.rs, validation_omega_tuning_cation.rs, validation_rsh_cation_stability.rs, gen_rsh_omega.py, gen_rsh_cation_stability.py
Constrained DFT (Becke charge and spin constraints): E(N) − E_unconstrained, λ, and dE/dN = −λLiH (Li), HF (F), H2O⁺ (O; charge and spin) / 6-31G, def2-SVP, 3–4 targets eachNWChem 7.2.2 cdft ... pop becke, PBE, Becke-partitioned Treutler grid, fed ferric's basis; internal: natural-target limit (λ = 0) and dE/dN = −λunconstrained E 1.0e-8 Ha; E(N) − E_unc 2.0e-7 Ha; λ 1.1e-6 (NWChem, grid limit); dE/dN = −λ to 2.3e-12 Ha (Simpson, def2-SVP)5e-8 Ha; 1e-6 Ha; 5e-6; 5e-11 Havalidation_cdft.rs, gen_cdft.py, NWChem inputs
Constrained DFT state selection at an integer target: which constrained state ferric lands on, seeded from NWChem's orbitals (solve_cdft_uhf_seeded, CdftSeed) and unseeded with and without the stability descentHeNe⁺ (N_He = 2, R = 2.0 Å), LiH⁺ (N_Li = 2.8, R = 3.0 Å) / def2-SVP, two states eachNWChem 7.2.2 cDFT (HF exchange), run both with its native Becke partition and with ferric's He/Ne radius ratio by relabelling the atoms; NWChem orbitals imported into ferric (AO order and the d(m=+1) sign mapped and checked)one-shot energy of NWChem's orbitals ≤ 4.7e-10 Ha; seeded and unseeded states ≤ 7.7e-10 Ha after the quadrature correction; λ 3.2e-5 (HeNe⁺), 2.9e-7 (LiH⁺)1e-8 Ha; 1e-8 Ha; 1e-4validation_cdft_state.rs, gen_cdft_state.py
cDFT-ET coupling (Wu–Van Voorhis, coupling_hab) and its ingredients: determinant overlap, one- and two-electron transition elements, |V|; KS-PBE diabats end to endHe₂⁺ at 2.50/3.00/3.50 Å / def2-SVP, aug-cc-pVDZNWChem 7.2.2 cDFT + et (its MO files recomputed in PySCF, every et number reproduced to ≤ 5.9e-11); the textbook form F = E + λN with its constraint-offset invariancekernel ≤ 5.9e-11 Ha; coupling vs textbook ≤ 2e-18; E − E_unc ≤ 4.0e-7 Ha; λ ≤ 6.6e-6; |S| ≤ 2.8e-3 rel., |V| ≤ 8.3e-4 rel. (both largest at def2-SVP 3.50 Å, where |S_AB| = 3.9e-4 is the smallest of the six points; the absolute S offset is a flat 1.7e-7–1.1e-6 across the row, and V ∝ S so its relative error is smaller than S's everywhere); all six points converge end to end, deepest inner SCF 106 iterations (cap 150); and from λ = 0 with no multiplier start, all six reach the same root to ≤ 3.5e-10 in λ and ≤ 1.6e-10 Ha in E, in 11–21 outer iterations against the 3–4 a start at the root needs2e-10 Ha; 1e-12; 2e-6 Ha; 3e-5; 4e-3 rel. (S); 2e-3 rel. (V)validation_cdft_et.rs, gen_cdft_et.py
IEF-PCM solver and SCF on PySCF's cavity; ferric's own cavity; the solver's analytic limitswater, NH3 / STO-3G, cc-pVDZ; ε = 78.4, 4.7. Limits: a unit point charge at d/R = 0, 0.25, 0.5 in one Lebedev sphere (50, 110, 302 points), ε = 78.4, 4; water's cavity for ε → ∞PySCF RHF.PCM() IEF-PCM; its SWIG cavity injected into ferric. Limits: the analytic Born and Kirkwood reaction-field energies, the Gauss-law charge −(ε−1)/ε, and the conductor equation S q = −vcharges 1.3e-17, E_pcm 3.5e-18 Ha (solver); total energy 4.8e-12 Ha (SCF); ferric's own cavity (modified-Bondi radii, 110-point spheres) 0.07–0.61% from PySCF. Limits at 302 points: Born 1.1e-8 (ε = 78.4) and 1.7e-7 (ε = 4) relative, Kirkwood ≤ 1.8e-4 relative at d/R = 0.5, each error falling monotonically 50 → 110 → 302; total charge within 1.3e-7 of Gauss's law; the conductor gap scales as 1 − f(ε) (ratio 100.000 between ε = 1e6 and 1e8)1e-11 Ha (solver); 1e-10 Ha (SCF); own cavity ±2%; limits: the a-posteriori estimate |E₃₀₂ − E₁₁₀| (|Q₃₀₂ − Q₁₁₀| for the charge), ratio within a factor 2validation_pcm.rs, gen_pcm.py, ief_limits.rs, pcm_ief_sympy.py
Geometry optimization, RHF and RKSH2O, NH3, CH2O from distorted starts / RHF and B3LYP 6-31G, PBE cc-pVDZPySCF analytic gradient (KS with grid response) driven to max abs gradient ≤ 1e-6 Ha/Bohr by scipy BFGS; exact J/Kdistances ≤ 2.6e-6 Bohr; angles ≤ 1.1e-4°; optimized energies ≤ 9.3e-13 Ha2e-5 Bohr; 1e-3°; 1e-10 Havalidation_geometry_optimization.rs, gen_geometry_optimization.py
Geometry optimization, UHF, ROHF and UKSHO2, CH3, NH2 from distorted starts / UHF, ROHF, UKS-PBE 6-31GPySCF analytic gradient driven to max abs gradient ≤ 1e-6 Ha/Bohr by scipy BFGS; stability() at start and enddistances ≤ 2.6e-6 Bohr; angles ≤ 1.1e-4°; optimized energies ≤ 9.3e-13 Ha; ⟨S²⟩ ≤ 4.9e-9 (UHF, UKS; ROHF is a pure spin state and is not checked)2e-5 Bohr; 1e-3°; 1e-10 Ha; 5e-8 (⟨S²⟩, UHF and UKS)validation_geometry_optimization.rs, gen_geometry_optimization.py
G0W0@HFH2O / cc-pVDZPublished MOLGW IP (11.97 eV); PySCF gw_ac (IP 12.160 eV)—0.30 eV (IP); 0.20 eV (PySCF LUMO and gap)h2o_g0w0_cohsex.rs
MP3 (RI integrals)H2O, NH3 / cc-pVDZ, def2-SVP; all-electron and frozen coreexact-integral PySCF RHF, then the same aux basis and DF factorization as ferric; two independent numpy MP3 constructions (spin-orbital textbook, closed-shell via PySCF's linear doubles residual), agreeing to 2e-17≤ 7.6e-12 Ha1e-10 Havalidation_cc.rs, gen_cc.py, reference data
RI-CCDH2O, NH3 / cc-pVDZ, def2-SVPexact-integral PySCF RHF, then the same aux basis and DF factorization as ferric; PySCF CCD on the DF integrals≤ 1.9e-11 Ha2e-10 Havalidation_cc.rs, gen_cc.py, reference data
RI-CCSD, spin-orbital and spin-adaptedH2O, NH3 / cc-pVDZ, def2-SVP; HCN / cc-pVDZ; H2O / aug-cc-pVDZ; C2H6 / cc-pVDZ (spin-adapted only)exact-integral PySCF RHF, then the same aux basis and DF factorization as ferric; PySCF CCSD on the DF integrals≤ 4.7e-11 Ha (both solvers)2e-10 Havalidation_cc.rs, gen_cc.py, reference data
CCSD(T), spin-orbital and spin-adapted (T)H2O / cc-pVDZ, aug-cc-pVDZ; HCN / cc-pVDZ; C2H6 / cc-pVDZ (spin-adapted only)exact-integral PySCF RHF, then the same aux basis and DF factorization as ferric; PySCF ccsd_t on the DF integrals(T) ≤ 6.9e-12 Ha; the two (T) codes agree to 8.9e-121e-10 Havalidation_cc.rs, gen_cc.py, reference data
CAS-CI (library only: ferric_ci::run_cas_ci, closed-shell RHF reference, lowest Ms = 0 root): total, core and active-space energiesN2 at r = 1.10 and 2.20 Å, CAS(6,6) / cc-pVDZ; H2O, CAS(4,4) / 6-31G; each also with the window moved down one orbitalPySCF mcscf.CASCI on an exact-integral RHF with the same orbital window, checked against a dense diagonalization of the full active-space Hamiltonian. At 2.20 Å both codes use the symmetric RHF, which is internally unstable toward a symmetry-broken RHF 0.19 Ha lowerE_CASCI ≤ 1.4e-11 Ha; e_core and the active-space energy ≤ 2.6e-10 Ha; E_RHF 2.2e-12 Ha. Slater–Condon signs: every element of general_element, sigma and the diagonal against an independent bit-loop second-quantized H on random asymmetric integrals (CAS(4,4) to CAS(6,6), odd and even N_α), ≤ 8.9e-15; an exact Jordan–Wigner derivation of the same convention3e-9 Ha (CAS-CI energies), 5e-11 Ha (E_RHF), 1e-12 (signs)validation_casci.rs, gen_casci.py, slater_condon_brute_force.rs, casci_slater_condon_phases_sympy.py
Point-group-blocked closed-shell RHF (ferric_scf::symmetric::solve_rhf_symmetric with RhfConfig.symmetry, optional irrep_nelec; Python run_rhf(symmetry=True), CLI [scf] symmetry with kind = "rhf"; Abelian subgroup of the molecule's point group; returns a SymmetricRhf whose AO matrices are in the symmetry frame): total energy, per-irrep occupation, and the irrep_nelec excited-state energyH2O (C2v), NH3 (Cs), C2H4 and benzene (D2h) / STO-3G, 6-31G; H2O with the out-of-plane 1b1 pair moved into a1 (irrep_nelec); H2 / cc-pVDZ at R = 50 and 10000 Å, sigma_g^2PySCF RHF with mol.symmetry = True and irrep_nelec, ferric's basis and geometry. D2h irrep labels depend on the detected axes, so D2h occupations are compared as a multiset. Negative control: without symmetry ferric ends not converged and far from sigma_g^2 (-0.71267 and -0.70740 Ha at R = 50 and 10000 Å); the value it stops at depends on the SCF trajectory and the BLAS kernel (PySCF without symmetry, recorded in the references: -0.42718 and -0.37335 Ha, also not converged)E ≤ 1.0e-11 Ha over all 10 blocked and 2 excited cases; identical per-irrep occupations; blocked vs unblocked energy ≤ 1.1e-13 Ha (STO-3G, same frame) and ≤ 7.8e-14 Ha (H2O, NH3, CH4, N2 / cc-pVDZ, same frame); max |[R,F]| ≤ 6.4e-14; H2 sigma_g^2 (in scf_symmetry.rs, not the ignored PySCF test) matches PySCF to 1.1e-16 (R = 50 Å) and 4.4e-16 Ha (R = 10000 Å)1e-8 Ha (PySCF, H2O/NH3/C2H4/benzene and the excited state), 1e-9 Ha (stretched H2 reference energies), 1e-10 Ha (blocked vs unblocked), 1e-10 (commutator)validation_scf_symmetry.rs, scf_symmetry.rs, gen_scf_symmetry.py
Point-group-blocked closed-shell RKS (solve_rhf_symmetric with RhfConfig.xc set; Python run_dft/run_ksdft(symmetry=True), CLI [scf] symmetry with kind = "ksdft" or "rhf" + [dft] functional; no Fock or density symmetrisation: the KS grid is built in the symmetry frame, where every Lebedev rule and Becke weight maps onto itself under the detected operations; a near-degenerate HOMO/LUMO pair in different irreps not listed in irrep_nelec is refused, and so is a main grid below 110 angular points, where the grid splits degenerate partners by more than the 1e-3 Ha check): PBE total energy and per-irrep occupationH2O (C2v) / STO-3G, 6-31G; H2 / cc-pVDZ at R = 50 and 10000 Å, sigma_g^2 and sigma_u^2 (irrep_nelec)PySCF dft.RKS PBE with mol.symmetry = True on ferric's KS recipe: (75, 110) unpruned grid, Becke partition with Becke radii adjustment, RI-J with def2-universal-jkfit from ferric's JSON. Each code builds its grid in its own symmetry frame; the frames differ by an axis permutation or sign (an Oh operation), so the grids coincide. Negative control: without symmetry ferric RKS ends not converged, more than 1e-3 Ha (in practice about 0.4-0.6 Ha) above sigma_g^2 at -0.91423 Ha; the value depends on the SCF trajectory and the BLAS kernel (PySCF without symmetry, recorded in the references: -0.41244 and -0.40191 Ha, not converged)E ≤ 3.6e-13 Ha over the 2 H2O and 4 H2 cases; identical per-irrep occupations; blocked vs unblocked energy ≤ 2.3e-13 Ha (H2O, NH3, C2H4, benzene / STO-3G PBE, same frame, RI-J; H2O / 6-31G also with exact J, 1.4e-14; H2O / STO-3G SCAN 5.7e-14 and ωB97X-V 1.4e-14); max |[R,F_KS]| ≤ 5.1e-13 (V_xc included); rotated and translated H2O input vs the unblocked input-frame energy 5.3e-5 Ha (the grid is not rotation invariant; the same-frame anchor holds)5e-12 Ha (PySCF), 1e-9 Ha (stretched H2 in scf_symmetry_ks.rs), 1e-10 Ha (blocked vs unblocked), 1e-10 (commutators), 1e-3 Ha (rotated input, documented)validation_scf_symmetry.rs, scf_symmetry_ks.rs, gen_scf_symmetry.py
Point-group-blocked open-shell UHF and ROHF (library only: ferric_scf::symmetric_open::solve_uhf_symmetric and solve_rohf_symmetric with RhfConfig.symmetry, optional per-irrep (n_alpha, n_beta) irrep_nelec argument; no Kohn-Sham; ROHF needs rohf_occupation_guard = false; return SymmetricUhf / SymmetricRohf, whose AO matrices are in the symmetry frame): total energy, per-irrep (alpha, beta) occupation, and the irrep_nelec excited-state energyNH2, CH3 (D3h, blocked in a C2v subgroup), NO2 (C2v) doublets, O2 (D2h) and CH2 (C2v) triplets / STO-3G, 6-31G (NO2 6-31G only); H2O+ with the hole moved from the out-of-plane 1b1 orbital to 3a1 (2A1 instead of 2B1, irrep_nelec); singlet H2 at 2.0 Å / 6-31G as the negative controlPySCF UHF and ROHF with mol.symmetry = True and irrep_nelec ({irrep: (n_alpha, n_beta)}), ferric's basis and geometry. D2h (and CH3, whose D3h has three equivalent C2v subgroups) irrep labels depend on the detected axes, so those occupations are compared as a multiset of (n_alpha, n_beta). Negative control: unblocked UHF on singlet H2 at 2.0 Å lands on a spatially symmetry-broken solution 0.0847 Ha below the symmetric sigma_g^2 state (ferric with scf_stability_descent -1.00093524 Ha, PySCF after following its instability -1.00093524 Ha), which the blocked solver cannot reach by construction (a blocked closed-shell UHF has identical alpha and beta blocks, so it is the restricted determinant; it returns the sigma_g^2 energy, equal to RHF)E ≤ 3.5e-12 Ha over all 11 UHF and 11 ROHF blocked cases and the 4 excited cases; identical per-irrep (alpha, beta) occupations; blocked vs unblocked energy ≤ 7.1e-14 Ha (STO-3G, same frame, scf_open_symmetry.rs); max |[R,F_alpha]|, |[R,F_beta]| ≤ 3.8e-15; ⟨S²⟩ blocked = unblocked to 1e-8; rotated and translated NH2 and O2 input vs the unblocked input-frame energy ≤ 5.7e-14 Ha (symmetrization shift ≤ 4.2e-16 Bohr)1e-8 Ha (PySCF, ground and excited), 1e-8 Ha (H2 negative control energies), 1e-10 Ha (blocked vs unblocked), 1e-9 Ha (rotated input), 1e-10 (commutators)validation_uhf_symmetry.rs, scf_open_symmetry.rs, gen_uhf_symmetry.py
G0W0 quasiparticle energies, HOMO−2 to LUMO+2, @HF and @PBE (and @HF with one frozen core orbital)H2O, NH3, N2 / cc-pVDZ (cc-pvdz-ri), aug-cc-pVDZ (aug-cc-pvdz-rifit); @PBE: H2O / both bases, N2 / cc-pVDZPySCF gw_ac fed ferric's basis and aux, with the same 100-point frequency grid, Padé nodes, full quasiparticle equation and exact-integral SCF; @PBE references apply ferric's XC density floor (ρ ≤ 1e-10) and evaluate the Padé as the standard Thiele fraction, because PySCF's pade_thiele_ndarray applies its last coefficient twice (up to 7.1e-3 Ha at @PBE)@HF ≤ 1.3e-7 Ha, @PBE ≤ 6.2e-8 Ha; Σc(ef + iω) at the Padé nodes ≤ 8.6e-11 Ha; Σx ≤ 4.9e-9 Ha1e-6 Ha (Σc(iω) 1e-9)validation_gw.rs, gen_gw.py
G0W0@HF with ECPsI2, Xe, Ag2 / aug-cc-pVDZ-PP (def2-tzvp-rifit)PySCF gw_ac, same basis, inline ECP and aux≤ 2.5e-6 Ha (ε_mf 2.9e-6 from the ECP SCF offset)2e-5 Ha″
U-G0W0@UHFOH, CH3, NH2 / cc-pVDZ, aug-cc-pVDZ; O2 and CH2 triplets / aug-cc-pVDZPySCF ugw_ac on a stability-checked UHF, with ferric's per-spin Fermi level≤ 8.7e-7 Ha; OH/cc-pVDZ 7.0e-6 Ha (its π hole makes the UHF marginally stable)3e-5 Ha″
U-G0W0@UKS/PBE (gw with [rpa] xc and multiplicity > 1)OH, CH3, NH2 / cc-pVDZ (cc-pvdz-ri)PySCF ugw_ac Σc(ef + iω) on a stability-checked exact-J UKS/PBE with ferric's per-spin XC density floor and per-spin Fermi level; standard Thiele continuation and the quasiparticle equation solved in numpy with the density-fitted Σx, because PySCF's ugw_ac vhf_df exchange has the wrong signΣc(ef + iω) ≤8.7e-9 Ha, v_xc ≤7.1e-10 Ha, quasiparticle energies ≤2.6e-8 Ha on orbitals whose quasiparticle equation has one root (NH2's α HOMO and HOMO−1 have three within 1 Ha and are not compared); ferric solves each spin's quasiparticle equation with Σx − v_xc inside it, as the reference does; adding the shift after an unshifted solve lands 0.38–0.96 eV away, and the test requires ferric to miss that rootΣc(ef + iω) 1e-7 Ha; v_xc 1e-8 Ha; quasiparticle energies 3e-7 Havalidation_gw.rs, gen_gw.py
COHSEX (@HF and @PBE), evGW₀, evGW (@HF)COHSEX: H2O, N2 / cc-pVDZ; evGW₀, evGW: H2O / cc-pVDZnumpy COHSEX on PySCF's density-fitted integrals; @PBE on the exact-J RKS/PBE orbitals with ferric's XC density floor, plus the static Σx − v_xc from PySCF's density-fitted exchange and v_xc; anchored to U-COHSEX on the same RKS orbitals as UKS (1.1e-15 Ha); evGW₀ and evGW by iterating PySCF's get_sigma with ferric's update ruleCOHSEX@HF 9.4e-10 Ha; COHSEX@PBE 1.2e-9 Ha; evGW₀/evGW 5.7e-7 Ha1e-8 / 1e-8 / 5e-6 Ha″
U-COHSEX@UHF (static, HF reference, spin-summed W)OH, CH3, NH2 / cc-pVDZ (cc-pvdz-ri), aug-cc-pVDZ (aug-cc-pvdz-rifit); O2 and CH2 triplets / aug-cc-pVDZ; H2O / cc-pVDZ singlet UHF against the closed-shell COHSEXnumpy U-COHSEX on PySCF's density-fitted integrals with ferric's aux, W from ε = I + Π_α + Π_β, on the stability-checked UHF of the U-G0W0 row; the singlet UHF numpy reproduces the closed-shell COHSEX reference to 2.1e-9 Haε_qp ≤ 3.3e-9 Ha (open shell); the singlet UHF through run_u_gw equals closed-shell COHSEX to 8.1e-103e-8 Ha (anchor 1e-8)validation_u_cohsex.rs, gen_u_cohsex.py, reference data
U-RPA, U-G0W0 and U-RI-MP2 (doubles only) on a ROHF reference, semi-canonicalized per spinOH, CH3 doublets / cc-pVDZ (cc-pvdz-ri)PySCF URPA, ugw_ac and a numpy UMP2 (checked against DFUMP2) on the ROHF orbitals semi-canonicalized in numpy (each spin's Fock diagonalized in its occupied and virtual blocks); the ROMP2 singles term is not included on either sideU-RPA E_c ≤ 7.3e-12 Ha, U-MP2 E_c ≤ 8.7e-12, U-G0W0 QP ≤ 1.5e-5 (OH β) and ≤ 1.3e-6 (CH3); semicanonical orbital energies ≤ 6.4e-101e-10 Ha (E_c), 5e-5 Ha (QP)validation_rohf_reference.rs, gen_rohf_semicanonical.py, reference data
BSE-TDA singlet excitation energies and oscillator strengths (G0W0@HF, static W, frozen core 0)H2O / cc-pVDZ (cc-pvdz-ri), aug-cc-pVDZ (aug-cc-pvdz-rifit); NH3, CH2O / cc-pVDZ; lowest five singletsnumpy BSE-TDA on PySCF density-fitted integrals with ferric's aux, W from the static RPA dielectric on HF energies; the kernel is diagonalized on ferric's own quasiparticle energies, because G0W0 energies of core and high virtual orbitals move by up to 0.29 Ha under a 1e-10 relative change of Σc(iω). The lowest five Ω are insensitive to that: two independent generator runs move individual far-from-Fermi quasiparticle energies by up to 3.8e-3 Ha and the lowest five Ω by at most 1.1e-7 Ha, because those states carry ≤ 7e-3 of their norm on the affected rows. Replacing the out-of-window quasiparticle energies by a rigid scissor outside HOMO−k…LUMO+k instead moves the lowest five Ω by 1.5e-4 to 9.4e-3 Ha (k = 2) and 1.4e-5 to 2.0e-4 Ha (k = 8), so every quasiparticle energy is solved and the poorly-determined ones are reported in BseResult::qp_suspect_mos rather than replaced; CIS limit (W replaced by the bare Coulomb interaction, HF energies) against the same numpy build and PySCF tdscf.TDAΩ ≤ 1.8e-10 Ha and oscillator strengths ≤ 2.6e-9 against the kernel on ferric's QP energies; ≤ 2.2e-7 Ha with each side's own QP energies; CIS anchor ≤ 1.6e-9 Ha vs numpy DF-CIS2e-9 Ha (Ω), 3e-8 (f), 2e-6 Ha (own QP)validation_bse.rs, gen_bse.py
RPAx static polarizability (run_rpax_static_polarizability, statically screened W(0) from full-rank PDEP, frozen core 0)H2O, NH3 / aug-cc-pVDZ (aug-cc-pvdz-rifit) at RHF, scissor 0; H2O / cc-pVDZ (cc-pvdz-ri) at PBE (exact J, (75,110) grid), scissor 0.36 Ha; full 3×3 tensornumpy assembly of the same A±B kernel on PySCF density-fitted integrals with ferric's aux, W(0) = Lᵀ(I + Π(0))⁻¹L by a direct inverse on the unshifted mean-field energies. The numpy bare-exchange limit (W replaced by the bare Coulomb interaction) equals PySCF's CPHF α from its own response operator to ≤ 1.9e-14 relative, both density-fitted and with exact integrals. ferric's bare-exchange limit is compared with the same numpy build; against exact-integral CPHF it differs by the density-fitting error of the aux (9.0e-5 H2O, 1.5e-5 NH3). The lowest eigenvalues of A+B and A−B are compared too, because the static α does not depend on A−Bα ≤ 5.3e-9 relative (worst element over max |α|), α_iso ≤ 2.8e-9; bare limit ≤ 5.4e-9; lowest A±B eigenvalues ≤ 7.5e-10 Ha5e-8 (α), 1e-8 Ha (eigenvalues)validation_rpax_alpha.rs, gen_rpax_alpha.py
TDA and Casida TDDFT excitation energieswater, formaldehyde, NH3 / 6-31G, aug-cc-pVDZ; r2SCAN (τ kernel): water / 6-31G on the flat 75 × 110 gridPySCF tddft.TDA/TDDFT, same RI and grid; r2SCAN: PySCF get_abHF ≤ 2e-6 eV; LDA/PBE ≤ 1.2e-5 eV; B3LYP ≤ 8.1e-4 eV on the default pruned grid, ≤ 2.8e-6 eV (water, formaldehyde / aug-cc-pVDZ) on the reference's flat grid; r2SCAN 2.1e-6 eV (TDA and Casida). PySCF fed ferric's grid coordinates and weights reproduces ferric's r2SCAN roots to 6e-8 eV and E_SCF to 1e-12 Ha on the flat and the default grid1e-3 eV; same grid 2e-5 eVvalidation_tddft.rs, gen_tddft_refs.py
RI-MP2 analytic gradient (exact-J/K RHF), all electrons and frozen coredistorted H2O, bent HCN / cc-pVDZ (cc-pvdz-ri); distorted NH3 / def2-SVP (def2-svp-rifit); frozen core: the same three, freezing the cores ORCA freezesORCA RI-MP2 NoRI NoFrozenCore EnGrad and frozen-core RI-MP2 EnGrad, with the same /C aux; PySCF DFMP2 5-point finite differences (all electrons)≤ 2.1e-9 Ha/Bohr (PySCF FD); ≤ 5.1e-8 Ha/Bohr (ORCA, its own floor); energies ≤ 1.3e-10 Ha. Frozen core: 4.9e-8 (H2O), 1.3e-8 (HCN), 1.7e-8 (NH3) Ha/Bohr vs ORCA, E_corr < 5e-11 Ha; H2O / STO-3G with O 1s frozen 2.0e-9 Ha/Bohr vs FD of ferric's own energy; without the core–active response block the H2O / cc-pVDZ gradient misses FD by 9.8e-52e-8 Ha/Bohr (FD), 2.5e-7 Ha/Bohr (ORCA); 1e-9 Ha; 1e-6 Ha/Bohr (own FD)validation_rimp2_gradient.rs, gen_rimp2_gradient.py
RI-MP2 relaxed dipole and finite-field relaxed polarizability (library: cpks_polar::static_relaxed_density_ao, ff_polar; all electrons)water / STO-3G (pinned) and cc-pVDZ (measured, not pinned); aux cc-pvdz-riPySCF finite-field DF-MP2 energy with the same aux basis (scripts/cpks/ff_dfmp2_dipole_alpha.py)relaxed μ_z 3e-8 a.u. (STO-3G), 2e-8 (cc-pVDZ); ff_polar relaxed α diagonal 3e-6 a.u. (STO-3G), α_zz 1.4e-5 (cc-pVDZ).; the analytic relaxed α (mp2_polarizability_analytic) is the next row1e-6 a.u. (μ); 1e-4 a.u. (α)cpks_polar.rs, ff_polar.rs, ff_dfmp2_dipole_alpha.py
Relaxed RI-MP2 static polarizability, analytic (mp2_polarizability_analytic → analytic_alpha_relaxed; library only, closed shell, no frozen core): dipole CPHF, first-order Fock response, central difference (h = 1e-4) of the relaxed density along the first-order field reference. Exactness anchor: the same difference of the SCF density equals the CPHF HF α to 4e-9 rel. ff_polar (finite field, re-converged SCF) is a cross-check. Attenuated correlation (Coulomb RHF, erfc(ωr) MP2) matches a 5-point finite field of that energyH2O / STO-3G, cc-pVDZ; NH3 in a generic orientation (all 9 elements) / cc-pVDZ; aux cc-pvdz-ri, exact-J/K RHFPySCF 2.13 finite-field −d²E/dF² of RHF + DF-MP2, same aux basis (5-point diagonal, 16-point mixed)≤ 2.2e-6 rel1e-5 × max(|α_ref|, α_iso)cpks_polar.rs (relaxed_alpha_matches_pyscf_energy_hessian), ff_dfmp2_dipole_alpha.py
IEF-PCM analytic nuclear gradient (ferric_pcm::pcm_gradient, threaded into the RHF/UHF/ROHF/KS gradients)water, C1 methanol, NH2 / STO-3G, ε = 78.4 (RHF, RKS-SVWN, UHF, ROHF; point and Gaussian probes; Lebedev 50, 110, 302)central FD of pcm_step's energy at fixed density and of the solvated SCF energy; PySCF 2.13 IEF-PCM nuc_grad_method() on ferric's 302-point cavity (= PySCF's gen_surface: tessera count, area, centroid to 1e-10); rigid-rotation FD for the torquePySCF: 4.2e-11 (water), 7.2e-11 (methanol) Ha/Bohr; FD: ≤ 2.0e-7 (fixed density), ≤ 2.9e-7 (SCF) Ha/Bohr; ε = 1 + 2e-9 vs vacuum 3.3e-12; Σ_A g = 0 per part; torque 1.36e-4 vs FD to 8e-9; nine single-term mutants (switching, area, S/D diagonal and off-diagonal, tessera-site, nuclear-tessera terms) each fail the FD tests1e-6 Ha/Bohr (PySCF); FD: |FD(2h) − FD(h)| plus a round-off / SCF-convergence floorpcm_gradient.rs (ferric-pcm), pcm_gradient.rs (ferric-scf), gen_pcm_grad.py
COSMO solvation (RHF, UHF; energy only, no gradient: gradient entries refuse it)H2O, NH3, CH3OH, acetate(−) / STO-3G, cc-pVDZ × ε 4.7, 78.4; HO2 (UHF) / cc-pVDZPySCF pcm.py COSMO driver with ferric's radii, 110-point cavity and point-charge potential (ferric's model); stock PySCF COSMO for the formulation gapsolvated energy ≤ 1.9e-11 Ha, E_cosmo ≤ 9.7e-11 Ha (ferric's model); 0.05–0.51% from stock PySCF COSMO (point vs Gaussian surface charges)1e-10 Ha; 1e-9 Ha; 2%validation_cosmo.rs, gen_cosmo.py
Attenuated RI-MP2 (erfc on 3-center and metric)H2O, NH3 / aug-cc-pVDZ + aug-cc-pVDZ-RIFIT, ω 0.2, 0.222, 0.42, 1.0 Bohr⁻¹numpy RI-MP2 on PySCF int3c2e/int2c2e under with_range_coulomb(-ω)E_corr ≤ 5.9e-12 Ha at every ω; ω → 0 reproduces Coulomb RI-MP2 to 1.6e-151e-10 Havalidation_attenuated_mp2.rs, gen_attenuated_mp2.py
MP2-V (att_mp2_vv10): damped VV10 half, terfc-attenuated MP2 half and erfc controlH2O / cc-pVDZ, aug-cc-pVDZ; NH3 / cc-pVDZ; water dimer (S22) / aug-cc-pVDZ (VV10 and erfc control only; the terfc reference has no dimer); r₀ = 1.00 Å (b 11.0) and 0.85 Å (b 8.0), C = 0.0089, ferric's 50 × 50 NLC grid, PySCF's RHF densityPySCF numint._vv10nlc (undamped); numpy VV10 from Vydrov & Van Voorhis 2010 with the 1 − terfc(R, r₀)² damping (damped); numpy RI-MP2 on PySCF with_range_coulomb(-ω) integrals, ω = 1/(r₀√2), frozen core (erfc control); numpy RI-MP2 on Fourier-space terfc integrals (PySCF ft_ao plus k-space quadrature; the SCS-MP2(2terfc) reference, reused, r₀ = 1.00 Å, frozen core 1; its terfc definition is taken from ferric's shim.cc, so only the r₀ → 0 and ∞ limits constrain ω = 1/(r₀√2)) (terfc)E_nl ≤ 3.1e-16 Ha undamped, damped and at the r₀ → 0 self-pair limit; terfc E_c, E_OS, E_SS ≤ 2.5e-12 Ha; erfc E_c ≤ 7.0e-12 Ha; E_nl on ferric's own density ≤ 1.12e-13 Ha; ∫ρ on the NLC grid ≤ 1.10e-13; a collapsed UHF singlet reproduces them (terfc E_c ≤ 9e-12 Ha). Controls: the erfc and Coulomb operators miss the terfc E_c by ≥ 1.0e-3 Ha (the test requires only ≥ 1e-8 Ha); mutants: erfc for terfc, r₀ in Å, Coulomb fitting metric5e-15 Ha (VV10); 1e-12 electrons (∫ρ on the NLC grid); 1e-12 Ha (E_nl on own density); 3e-11 Ha (terfc E_c); 1e-10 Ha (erfc E_c, E_HF)validation_mp2_v.rs, gen_mp2_v.py, gen_terfc_integrals.py
SCS-MP2(2terfc): terf/terfc 3-index (P|μν) and 2-index (P|Q) integrals, element-wise by shell class (s through f), and E_OS, E_SS (r₀ = 0.75, 1.00, 1.05 Å) with the Eq. 12 total (scs_mp2_2terfc, published defaults)H2O, NH3 / cc-pVDZ + cc-pVDZ-RI; H2O / aug-cc-pVDZ + aug-cc-pVDZ-RIFIT; frozen core 0 and 1Fourier-space terf, (2π)⁻³∫d³k (4π/k²)e^(−k²/4w²)cos(kr₀) Re[χ̂_P* ρ̂_μν], on PySCF ft_ao/ft_aopair and a Gauss–Legendre × Lebedev k-grid; terfc = PySCF analytic Coulomb − terf; numpy RI-MP2 with the terfc metric on PySCF exact-J/K RHF orbitals. Shares none of ferric's McMurchie–Davidson pass or tables. The same k-grid with the erf kernel reproduces PySCF with_range_coulomb(w) integrals to ≤ 3.4e-12, and the s-type terf/terfc elements match a 1-D real-space Gaussian-blob quadrature (no Fourier transform) to ≤ 3.5e-12; the generator refuses to write above 5e-113-index ≤ 1.4e-13 and 2-index ≤ 2.6e-12 in every shell class; E_OS, E_SS and the Eq. 12 correlation energy ≤ 3.3e-12 Ha; terfc at r₀ = 0.75 Å misses the 1.05 Å reference by 5.2e-3 Ha or more, and erfc at the same curvature misses terfc by 4.1e-2 Ha or more. the published parameters (aug-cc-pVTZ, frozen core, no counterpoise) are exercised only on an S66 subset (n=5, H-bonded/mixed, S66x8 geometry; no external anchor for the counterpoise path)2e-12 (3-index), 3e-11 (2-index); 5e-11 Havalidation_terfc_integrals.rs, validation_scs_mp2_2terfc.rs, gen_terfc_integrals.py, reference data
QM/MM electrostatic embedding (RHF): energy, embedding shift, QM gradient, MM forcesH2O + 10 charges / cc-pVDZ, aug-cc-pVDZ; CH3OH + 501 TIP3P charges / 6-31GPySCF qmmm.mm_chargeenergy and shift ≤ 4.8e-12 Ha; QM gradient ≤ 1.2e-10, MM forces ≤ 4.4e-12 Ha/Bohr (up to 501 charges)5e-11 Ha; 1e-9, 5e-11 Ha/Bohrvalidation_qmmm.rs, gen_qmmm.py
Gaussian-smeared MM charges (width in Bohr, ζ = 1/width², same as PySCF radii with unit="Bohr")H2O + 10 charges / cc-pVDZ, widths 0.5, 1, 2 BohrPySCF qmmm.mm_charge(radii=)energy ≤ 4.1e-12 Ha; QM gradient ≤ 1.2e-10 Ha/Bohr; width → 0 reproduces point charges to 3.0e-12 Ha5e-11 Ha; 1e-9 Ha/Bohr″
KS QM/MM (exact J/K, matched (75,110) grid; gradient with grid response)CH3OH + 20 charges / def2-SVP, B3LYP and PBEPySCF qmmm.mm_charge on dft.RKS, grid_response=Trueenergy ≤ 4.1e-12 Ha; QM gradient ≤ 2.9e-9, MM forces ≤ 1.8e-10 Ha/Bohr1e-10 Ha; 3e-8, 2e-9 Ha/Bohr″
Thole polarizable embedding (induced point dipoles, exponential Thole damping a = 2.1304): energy, E_pol, induced dipoles, QM gradient, MM rowsH2O + 4 TIP3P waters (α O 0.837, H 0.496 ų; with and without intramolecular exclusions) / cc-pVDZ, RHF; NH2 + 3 waters / cc-pVDZ, UHF; anchors: one site 30 Å away (classical limit −½α|E0|²), α → 0 (PySCF qmmm.mm_charge)numpy induction model on PySCF point-dipole field integrals and qmmm.mm_charge, self-consistent in the SCF; gradients by 5-point finite differencetotal energy ≤ 3.7e-12 Ha, E_pol ≤ 1.5e-9, induced dipoles ≤ 1.5e-9 a.u., QM and MM gradients ≤ 3.0e-9 Ha/Bohr; far-site E_pol within 7.7e-8 (relative) of −½α|E0|²5e-11 Ha (total), 1e-8 (E_pol, dipoles), 3e-8 Ha/Bohrvalidation_thole.rs, gen_thole.py
QM/MM geometry optimization (optimize_qmmm): stationarity of the returned geometry and the full-structure gradient (link (1−g)/g fold, RC/RCD midpoint ½/½ fold, MM force field)Capped ethane (QM CH3, link at the C–C cut) RHF × Keep, delete-host (Z1), RC / STO-3G, realistic MM LJ (C σ 3.40 Å, H 2.65 Å; every QM–MM pair excluded or 1-4 scaled), Z1 also 6-31G, move_mm all; ethyl radical (QM CH2•, capped CH3• doublet) UHF × Z1, RC, all; RKS/PBE and UKS/PBE (DF-J/K def2-universal-jkfit) × RCD × ("within", r) with the M2 hydrogens frozen; capped ethane + 2 TIP3P-charged waters selected by whole residue × RCD × ("residues", [...])None exists: no other code defines this energy surface (partition, link scale and boundary scheme are not standardized). Internal two-path check: central finite difference of the energy the optimizer minimizes, partition rebuilt at each displacement, against the analytic gradient. Both paths share the SCF, integrals, DF fit and grid; partition-construction errors are covered by charge-bookkeeping assertions and scheme-swap controls instead. With no MM atoms, each of RHF/UHF/RKS/UKS reproduces the plain optimizer bit for bitFree rows at the returned geometry ≤ 4.4e-6 Ha/Bohr (frozen rows ≥ 3e-3, as they must be); FD vs analytic ≤ 1.8e-8 Ha/Bohr at the minimum and ≤ 5.8e-7 relative off it (PBE ≤ 8.4e-8). Gradients of the wrong scheme, of the field switched off, or with an MM-row fold broken miss by ≥ 3.5e-3 relative; Keep (host charge 0.44 Å from the link H, q = −0.1) converges in 13 steps. The residue system has no bound minimum with its generic force field, so only its free-set behaviour and off-minimum gradient are checkedper case, ~10× measured: stationarity 9.6e-6 to 4.4e-5 Ha/Bohr; FD 1.1e-7 to 1.8e-7 Ha/Bohr (minimum), 5.3e-7 to 5.8e-6 relative (off)validation_optimize_qmmm.rs, qmmm_gradient_at_minimum.rs
MM force field (ferric-mm): bond, angle, torsion, Coulomb and LJ energies and gradients, exclusion and 1-4 pair setsALA-ALA, ACE-PHE-NME, TRP-PRO-ASP (charge −1), ACE-PHE-NME + 3 TIP3P waters, at the relaxed geometry and two random displacements; one extra case with every torsion phase shifted by 37°OpenMM Reference platform (double precision), ff14SB and flexible TIP3P, no cutoff (the default; PME / Ewald and periodic images are not implemented); Coulomb and LJ split by evaluating copies of the nonbonded force with epsilons or charges zeroedenergies ≤ 1.75e-12 relative, gradients ≤ 8.5e-11 relative (after OpenMM's Coulomb constant, which differs from ferric's by 6.6e-11 relative); pair sets identical. Near φ = 0 and π and near-linear valence angles the torsion gradient matches central FD within a tolerance derived from the step (truncation + round-off); an exactly linear angle (and a torsion through a collinear angle) gives a finite, exactly zero gradient1e-11 (energy), 1e-9 (gradient), relative; FD: the derived step tolerancevalidation_mm.rs, gen_mm.py, terms_fd.rs, mm_dihedral_gradient_sympy.py
MM nonbonded cutoff (ferric-mm, opt-in NonbondedMethod::CutoffNonPeriodic, Rust library only): reaction-field Coulomb, LJ truncated or switched, 1-4 pairs uncut, cell listEthanol + 12 TIP3P-charged waters (45 atoms, diameter 14.2 Å): cutoff 5 Å with ε_rf 78.3, 78.3 + LJ switch from 4 Å, and 1; cutoff 2.9 Å with a switch from 2.2 Å (two 1-4 pairs beyond the cutoff)OpenMM NonbondedForce CutoffNonPeriodic, Reference platform, charges rescaled to OpenMM's Coulomb constant; anchors: cutoff beyond the diameter with ε_rf = 1 equals the all-pairs sum bit for bit (LJ energy, gradient) and Coulomb minus exactly c_rf Σ q_i q_j; the default all-pairs path is unchanged bit for bit; cell list equals the brute-force pair setenergy ≤ 2.4e-14, gradient ≤ 2.4e-15 relative; central FD with pairs inside, in the switching region and beyond the cutoff ≤ 1.8e-11 Ha/Bohr1e-12 relative (energy, gradient); FD 1e-10 Ha/Bohrcutoff.rs, gen_openmm_mm_cutoff_refs.py, mm_cutoff_sympy.py
QM–MM Lennard-Jones across a covalent cut (qmmm_mm_terms): 1-2/1-3 QM–MM pairs excluded, 1-4 pairs scaled by the topology's scale_lj_14, both from the full bond graphEthanol cut at C–C (realistic LJ, C σ 3.4 Å), QM = CH2OH and QM = CH3 at 1-4 LJ scale 0.5, QM = CH2OH at 0.75; every pair class (1-2, 1-3, 1-4, 1-5) present across the cutOpenMM Reference platform, exceptions from OpenMM's own createExceptionsFromBonds, charges zero; QM–MM part isolated as E(all) − E(QM ε = 0) − E(MM ε = 0). With no bond path between the regions the result is bit-identical to the every-pair sumenergy ≤ 8.7e-19 Ha, gradient ≤ 2.0e-18 Ha/Bohr; FD of the MM terms across the cut ≤ 1.5e-11 Ha/Bohr. The every-pair sum misses OpenMM by 6839 kcal/mol1e-14 Ha, 1e-14 Ha/Bohr; FD 1.5e-10 Ha/Bohrqmmm_lj_vs_openmm.rs, gen_openmm_qmmm_lj_refs.py
External potential in the RI-MP2 analytic gradientH2O + 10 charges / cc-pVDZ; CH3OH + 20 charges / 6-31G; aux cc-pVDZ-RI5-point finite difference of PySCF DFMP2 on qmmm.mm_charge RHF, same auxenergies ≤ 8.4e-12 Ha; RI-MP2 gradient ≤ 4.8e-9 Ha/Bohr vs PySCF FD1e-10 Ha; 3e-8 Ha/Bohrvalidation_qmmm_mp2_gradient.rs, gen_qmmm.py
κ-regularized RI-MP2 (κ = 0.5, 1.1, 2.0 Eh⁻¹; opposite- and same-spin parts)H2O, CH4 / cc-pVDZ (cc-pvdz-ri)numpy (1 − exp(−κΔ))² sum on PySCF density-fitted integrals with the same aux basis; κ → ∞ checked against PySCF DFMP2E_corr (OS, SS, total) ≤ 3.3e-12 Ha at κ = 0.5, 1.1, 2.0; κ → ∞ equals RI-MP2 exactly3e-11 Havalidation_kappa_mp2.rs, gen_kappa_mp2.py
AO-Laplace RI-MP2 (compute_mo and compute_ao, n_quad 3/5/7, all electrons)H2O / cc-pVDZ and aug-cc-pVDZ, CH4, s-trans-1,3-butadiene, n-octane / cc-pVDZ (cc-pvdz-ri, aug-cc-pvdz-rifit)Two numpy references on PySCF density-fitted integrals with the same aux basis: the dense (i,a,j,b) sum with the exact 1/Δ (equal to PySCF mp.dfmp2.DFMP2 to 6.7e-16), and the same sum with 1/Δ → Σ_k w_k e^(−t_k Δ) over the identical minimax nodes parsed from minimax.rs, which isolates quadrature error from implementation errorMO ≡ AO ≤ 3.4e-13 Ha; vs the quadrature-isolated reference (implementation error) ≤ 3.1e-11 Ha; vs exact DF-MP2 at n_quad = 7 (quadrature error) 8.0e-9 (CH4, R = 19.0), 4.0e-8 (octane, R = 24.5), 1.5e-7 (butadiene, R = 33.7), 8.3e-8 (H2O, R = 36.4), 2.2e-7 (H2O/aug, R = 45.7) — the error tracks R, and no case approaches the k = 7 table limit R ≤ 10001e-10 Ha (MO ≡ AO); 1e-9 Ha (vs quadrature reference); 2e-6 Ha (vs exact DF-MP2)validation_laplace_mp2.rs, gen_laplace_mp2.py, reference data, validation_laplace_energy_is_thread_independent.rs
Local RI-MP2 (rimp2 with [local] scheme = "amplitude-threshold"), in-core (global and per-pair domain fit) and integral_direct, at ε = 0 with every locality map at its no-op limit; finite ε as a measured error mapH2O, n-butane / cc-pVDZ, 6-31G (cc-pvdz-ri), frozen core 0 and one per heavy atom; finite ε on n-butane / cc-pVDZPySCF DFMP2 with the same aux basis (canonical orbitals, closed-form denominators), checked against a dense numpy sum to 1e-12. ORCA 6.1.1 DLPNO-MP2 (NormalPNO, TightPNO) on n-butane and n-octane / cc-pVDZ is recorded as a ballpark only: it truncates by PNO occupation, not by an amplitude threshold, so the two recoveries are not comparedE_corr ≤ 1.38e-11 Ha at ε = 0 (24 comparisons), at the default PCG stop of relres 1e-6. At ε = 1e-5, 1e-4, 1e-3 (n-butane, all electrons) ferric recovers 99.98%, 99.67%, 95.0% of DF-MP2 in-core and 99.98%, 99.65%, 95.0% integral-direct with its production maps; ORCA DLPNO-MP2 recovers 99.97% (NormalPNO) and 99.99% (TightPNO) of its own RI-MP21e-10 Ha (ε = 0); finite ε: under-correlation that grows with ε, no tolerancevalidation_lmp2_amplitude.rs, gen_lmp2_amplitude.py
DLPNO-MP2, pair-driven library path (dlpno_mp2_from_b_ov; library only, canonical occupieds). Untruncated (DlpnoConfig::exact()) against PySCF, then a measured truncation sweep over t_cut_pno and t_cut_pairsn-butane, n-hexane, n-octane / cc-pVDZ (cc-pvdz-ri), frozen core 0 and one per carbonPySCF DFMP2 with the same aux basis (checked against a dense numpy sum to 1e-12) for the untruncated number. ORCA 6.1.1 RI-MP2 cross-checks PySCF to 4.0e-8 Ha. ORCA DLPNO-MP2 (Normal/TightPNO) and, for n-butane only, DLPNO-CCSD(T) against canonical CCSD(T) are recorded as contextUntruncated DLPNO and canonical RI-MP2 agree with PySCF to 3.6e-11 Ha (12 comparisons). Recovery of ferric's own canonical RI-MP2 (all electrons, C4 / C6 / C8): 99.98 / 99.95 / 99.92% at t_cut_pno = 1e-8, 99.75 / 99.48 / 99.10% at 1e-7, 97.2 / 95.3 / 93.4% at 1e-6, monotone in both thresholds and never above 100%. ORCA DLPNO-MP2 recovers 99.975 / 99.965 / 99.956% (NormalPNO) and 99.988 / 99.983 / 99.980% (TightPNO) of ORCA RI-MP2. ORCA DLPNO-CCSD(T) on n-butane recovers 99.87% (Normal) and 99.95% (Tight) of canonical CCSD(T); that canonical CCSD(T) uses exact integrals while DLPNO uses RI, so those percentages include the RI error. Different localization, different thresholds: the two sets of percentages are not a ranking, and the all-electron columns are lopsided, because ORCA applies a separate TCutPNO (core) of 1e-10 (against 1e-8 for valence at NormalPNO, observed for n-butane) while ferric applies one flat t_cut_pno to every pair (n-butane at 1e-8: 99.9957% frozen-core against 99.977% all-electron; compare the frozen-core rows in the JSON for the cleaner side-by-side). The pair screen t_cut_pairs is uninformative at C4–C8: all-electron it removes only core pairs (at 1e-4, 13 / 21 / 30% of pairs for C4 / C6 / C8, costing 0.22 / 0.44 / 0.82% of E_corr), frozen-core it keeps every valence pair. With ferric's own Boys-localized occupieds the same screen drops pairs (next row); ORCA's pair prescreen is a different criterion and was not re-run. No speedup is claimed and ferric has no DLPNO-CCSD(T) driver4e-10 Ha (untruncated vs PySCF); ORCA RI-MP2 vs PySCF 4e-7; truncated runs: monotone, one-sided, and the t_cut_pno = 1e-6 run must miss the reference by more than the anchor bar with virtual retention below 1validation_dlpno_mp2.rs, gen_dlpno.py
DLPNO-MP2 pair screen on Boys-localized occupieds (dlpno_mp2_localized_from_b_ov; library only, closed shell, pair screening only: full canonical virtuals, no PNO/PAO truncation). Occupieds localized block by block (core, valence), Fock-coupled Jacobi amplitude solve over the retained pairs, pair retention and recovered E_corr against ferric's canonical RI-MP2 at t_cut_pairs = 1e-3 to 1e-6water, n-butane, n-hexane, n-octane / cc-pVDZ (cc-pvdz-ri), frozen core 0 and one per carbonferric's own canonical RI-MP2 (the energy is invariant to the occupied rotation, so the untruncated localized solve must reproduce it); a numpy prototype (proto_dlpno_loc_occ.py) of the formulation exists but its output is not committed and it compares only against its own canonical sum. No ORCA comparisonUntruncated localized solve == canonical RI-MP2 to 3.2e-12 Ha (water), 1.9e-12 / 3.9e-12 / 5.9e-12 Ha (C4 / C6 / C8); the residual is the Jacobi tolerance (2.8e-15 at a 1e3 tighter threshold). Pair retention at t_cut_pairs = 1e-4 (all electron, C4 / C6 / C8): 0.667 / 0.511 / 0.410, recovering 99.90 / 99.81 / 99.76% of E_corr (errors 0.58 / 1.7 / 2.8 mHa, one-sided); frozen core: 0.901 / 0.716 / 0.585, recovering 99.93 / 99.84 / 99.80%. At t_cut_pairs = 1e-5 (C4 / C6 / C8): all-electron retention 0.725 / 0.600 / 0.494 with errors 0.19 / 0.50 / 0.88 mHa; frozen-core retention 1.000 / 0.868 / 0.729 with errors 0 / 0.18 / 0.43 mHa. The C8 all-electron error at 1e-4 is 2.8 mHa (~1.8 kcal/mol), so 1e-4 is not chemical accuracy; 1e-5 is the more useful threshold. The screen is informative with localized occupieds, unlike the canonical row above. C4-C8 are below the locality onset (~30 Bohr), so these retentions describe these chains only. Opt-in add-back (add_back_truncation: the screened pairs' diagonal-f estimate is added to the result; cost one (ia|jb) block per discarded pair, at most one extra screen pass) cuts the all-electron error at every threshold measured: 1e-4 gives 0.58 / 1.68 / 2.80 mHa without and 0.12 / 0.40 / 0.68 mHa with (C4 / C6 / C8), 1e-5 gives 0.19 / 0.50 / 0.88 mHa without and 0.007 / 0.058 / 0.131 mHa with; gains 4.1x to 26x. There was no overshoot in the nine measured runs (C4–C8, cc-pVDZ, all electron, 1e-3 to 1e-5); the estimate is not a bound, so this is not a general guarantee. At 1e-3 the corrected error is still 5.4 / 9.4 / 13.3 mHa, so 1e-3 stays unusable. Frozen core was not measured with add-back. It is bit-identical when no pair is screened. The Jacobi conv_thresh default 1e-10 was checked: 1e-9 saves 3 of 23 iterations and moves the energy by at most 5.9e-8 mHa, so the default is unchanged. Mutation ledger in dlpno_localized_occupied.rs. No speedup, no PNO-level result, no ORCA ranking1e-10 Ha (anchor, ~17x the largest measured floor); screened runs one-sided, monotone in t_cut_pairs, and above the anchor bar whenever pairs are dropped; retention at 1e-4 below 1 for C6 and C8 and falling with chain length; add-back gain at least 2x with no overshootvalidation_dlpno_localized.rs, dlpno_localized_occupied.rs
LinLCCD(hh) correlation energyH2O, NH3 (RHF); OH (UHF, stability-checked) / 6-31G, cc-pVDZ (cc-pvdz-ri)numpy exact linear solve of the hole–hole ladder equations on PySCF density-fitted integrals; ladder off checked against PySCF DFMP2/DFUMP2LinLCCD(hh) ≤ 4.4e-13 Ha closed shell, 1.2e-12 Ha OH UHF; ladder off equals RI-MP2 to 1.1e-161e-11 Havalidation_linlccd.rs, gen_linlccd.py
Amplitude-threshold LinLCCD at ε = 0, all three variants (drivers-only, hh, full), global and integral-direct paths, plus exact canonical LinLCCD of each variantH2O, NH3 (RHF) / 6-31G, cc-pVDZ (cc-pvdz-ri)numpy exact linear solves on PySCF density-fitted integrals: hh in the occupied-pair eigenbasis, full (hh + pp ladders) as a Sylvester equation in the joint eigenbasis, each in spin orbitals and spin-adapted (agree ≤ 1.1e-16); no ladder checked against PySCF DFMP2local ≤ 4.4e-13 Ha (drivers-only), 2.8e-13 (hh), 2.0e-13 (full); integral-direct ≤ 3.0e-13; exact canonical ≤ 4.4e-13; at finite ε (NH3/cc-pVDZ, hh) the error is +1.6e-13 at ε = 1e-6, +2.7e-9 at 1e-5, +1.07e-5 Ha at 1e-45e-12, 3e-12, 2e-12 Ha; 3e-12; 5e-12validation_linlccd_amplitude.rs, gen_linlccd.py
ωB97X-L-V components: E_KS (libxc ωB97X-V form with the paper's 18 parameters, VV10 b = 10, C = 0.01), λ·E_c = LinLCCD(hh) under λ·erfc(ωr)/r (λ = 0.6, ω = 0.1), total; Be₂ bond energy E(10 000 Å) − E(r_e)H2O (RKS, exact J and RI-J), OH (ROKS → semicanonical → unrestricted) / def2-SVP (def2-svp-rifit, def2-universal-jkfit); Be₂ / def2-QZVPPDPySCF KS with register_custom_functional_ ext params on ferric's (75,110) grid; numpy LinLCCD(hh) on PySCF density-fitted λ·erfc integrals; ladder off equals λ² × PySCF SR-MP2; Be₂: paper Table 4 (2.3 kcal/mol)E_KS ≤ 1.05e-9 Ha; λ·E_c on the same orbitals 7.2e-14 Ha; total on ferric's orbitals 1.1e-7 Ha; Be₂ 2.299 kcal/mol1e-8; 1e-11; 5e-7 Ha; 0.3 kcal/molvalidation_wb97xlv.rs, wb97x_l_v_be2_bde.rs, gen_wb97xlv.py
OO-RI-MP2 energy and analytic nuclear gradient (all electrons, exact-J/K Fock, RI correlation)distorted H2O, distorted NH3 (RHF, energy and gradient); CH3 doublet (UHF, energy) / cc-pVDZ (cc-pvdz-ri)Energy: an independent numpy OO-RI-MP2 on PySCF integrals (exact J/K, the same aux, minimized to max orbital gradient ≤ 1e-10, anchored to PySCF SCF + DF-MP2 at zero rotation); cross-check: ORCA OO-RI-MP2 NoRI NoFrozenCore with the same /C aux, which stops 3.7e-8 to 7.0e-8 Ha above the minimum (reference/doubles split off by up to 1.5e-5 Ha). Gradient: 5-point finite difference of ferric's own OO energy; loose cross-checks against the finite difference of ORCA's OO energy and ORCA's analytic EnGrad (which misses that finite difference by up to 8e-6 Ha/Bohr)total 7.5e-13 Ha, reference/doubles split 6.4e-10 Ha vs numpy; gradient 8.0e-9 Ha/Bohr vs own FD, 2.0e-7 vs the FD of ORCA's energy1e-11 Ha (total), 5e-9 Ha (split), 2e-7 Ha (vs ORCA); 1e-7 Ha/Bohr (own FD), 1e-6 Ha/Bohr (ORCA FD)validation_oo_rimp2.rs, gen_oo_rimp2.py, ORCA inputs
Unrestricted RI-MP2 (rimp2 with multiplicity > 1): E_corr and the αα, ββ, αβ blocksOH, CH3, NH2, O2 (triplet), HO2 / cc-pVDZ (cc-pvdz-ri); HO2 also with frozen core 2PySCF DFUMP2 with the same aux on a stability-checked exact-integral UHF; αα and ββ split by an independent numpy build anchored to DFUMP2's same-spin and opposite-spin energies to 1e-11 HaE_UHF ≤2.8e-12 Ha; every MP2 energy ≤9.2e-10 Ha (HO2), ≤5.8e-11 Ha elsewhere3e-11 Ha; 1e-8 Havalidation_u_rimp2.rs, gen_u_rimp2.py
OO-RI-MP2 with a def2 ECP: energy and analytic nuclear gradient (core Hamiltonian includes V_ECP; gradient includes the ECP derivative term)HI at 1.75 Å (RHF) / def2-SVP (def2-svp-rifit), gradient on the I atomEnergy: the independent numpy OO-RI-MP2 extended with PySCF's ECP core Hamiltonian (ECP read from ferric's basis JSON), anchored to PySCF RHF + DF-MP2 at zero rotation; gradient: 5-point finite difference of ferric's own OO energyOO total 1.5e-8 Ha (the ECP integral offset the RHF energy also carries); doubles 5.5e-11 Ha; gradient 2.3e-9 Ha/Bohr vs its own finite difference2.5e-7 Ha; 5e-8 Ha/Bohrvalidation_oo_rimp2_ecp.rs, gen_oo_rimp2_ecp.py, zero-rotation anchors in oo_rimp2_ecp.rs
Open-shell PDEP-RPA correlation energy (UHF reference, full rank, spin-summed dielectric)OH, CH3, NH2 (doublets), O2 (triplet) / cc-pVDZ (cc-pvdz-ri); OH / aug-cc-pVDZ (aug-cc-pvdz-rifit); H2O / cc-pVDZ through both the restricted and the unrestricted pathPySCF gw.urpa.URPA (and gw.rpa.RPA for H2O) on the same stability-checked exact-integral UHF, same aux basis and the same 40-point Gauss–Legendre frequency grid; cross-checked by numpy on PySCF's density-fitted integrals; truncation checked against a numpy emulation≤ 9.2e-11 Ha (OH/aug-cc-pVDZ); truncated 6.4e-11; U path vs R path at the closed-shell limit 3.2e-121e-9 Ha (U vs R 3e-11)validation_urpa.rs, gen_urpa.py
dRPA@HF by the drCCD Riccati solve (drpa, exact) and its local approximation ([local] amplitude threshold); closed shell, frozen core 0, 1 and (n-butane) 4H2 / STO-3G (STO-3G aux); H2O, n-butane / 6-31G, cc-pVDZ (cc-pvdz-ri); n-octane / 6-31G (cc-pvdz-ri), carbon cores frozen, ε = 0 to 1e-3numpy plasmon dRPA on PySCF density-fitted integrals with the same aux basis and an exact-J/K RHF, Fock semicanonicalized, Ω² from a symmetric eigensolve; the formula and its frozen cores match PySCF gw.rpa.RPA on the same integrals (≤ 3.6e-11 Ha at 160 frequency points) and the H2 value matches the proof notebook's −0.0126072623 (3.7e-11)ε = 0: ≤ 3.0e-11 Ha on the CLI's exact route, the integral-direct route and the in-crate canonical plasmon reference. n-octane, ε = 1e-6 / 1e-5 / 1e-4 / 1e-3: error +6.7e-7 / +3.9e-5 / +5.5e-4 / +1.0e-2 Ha (less correlation) at 82 / 50 / 18 / 2.8% of amplitudes kept; there is no external finite-ε reference3e-10 Ha (ε = 0)validation_drpa_amplitude.rs, gen_drpa_amplitude.py; references in testdata/reference/validation/drpa_amplitude/
Attenuated PDEP-RPA correlation energy (erf and erfc on the 3-center integrals and the metric; closed shell, full rank)H2O, NH3 / cc-pVDZ (cc-pvdz-ri); H2O / aug-cc-pVDZ (aug-cc-pvdz-rifit); ω 0.2, 0.222, 0.42, 1.0 Bohr⁻¹numpy dRPA on PySCF int3c2e/int2c2e under with_range_coulomb(±ω), the same metric factorization as ferric (eigh with a 1e-10 cutoff for erf, Cholesky for erfc), the same RHF and the same 40-point Gauss–Legendre grid; the Coulomb kernel matches PySCF gw.rpa.RPA to 1.6e-14 Ha; erfc(ω → 0) and erf(ω → ∞) reproduce Coulomb RPA≤ 5.8e-11 Ha (erf), 3.5e-11 (erfc), 4.5e-11 (Coulomb); the ω → 0 erfc and ω → ∞ erf limits equal Coulomb RPA to 4.9e-111e-9 Ha (erf), 5e-10 (erfc, Coulomb, limits)validation_attenuated_rpa.rs, gen_attenuated_rpa.py
RS-MP2-RPA correlation energy, formulations B (DeltaLr: E_MP2[Coulomb] + E_dRPA[erf] − 2·E_OS[erf]) and T (CoupledRings: E_MP2[Coulomb] + ΔdRPA[Coulomb] − ΔdRPA[erfc]); every reported componentH2O, NH3 / cc-pVDZ (cc-pvdz-ri); H2O / aug-cc-pVDZ (aug-cc-pvdz-rifit); ω 0.222 (= 0.420 Å⁻¹, the default) and 0.42 Bohr⁻¹numpy assembly on PySCF int3c2e/int2c2e under with_range_coulomb(±ω): RI-MP2 spin components and dRPA from the same fitted integrals ferric uses per operator (eigh with a 1e-10 cutoff for erf, Cholesky otherwise), 40-point Gauss–Legendre grid; Coulomb pieces match PySCF DFMP2 to 3.3e-16 Ha and gw.rpa.RPA to 1.7e-14 Ha; the frequency-integrated second-order ring term equals 2·E_OS to 3.2e-15 relative; exact limits B, T(ω → 0) = MP2 and B, T(ω → ∞) = MP2 + ΔdRPA[Coulomb]every component ≤ 4.7e-11 Ha (MP2 pieces 4.2e-11, dRPA pieces 2.9e-11, B/T e_corr 4.3e-11); ω → 0 and ω → ∞ limits to 4.3e-115e-10 Havalidation_rs_mp2_rpa.rs, gen_rs_mp2_rpa.py
Closed-shell RPA nuclear gradient (total_rpa_gradient: central finite difference of E_RHF + E_c^RPA, exact-J/K RHF reference, full rank; all electrons and one frozen core orbital)distorted H2O, distorted NH3 / cc-pVDZ (cc-pvdz-ri)finite differences of PySCF exact-integral RHF + gw.rpa.RPA with the same aux basis and the same 40-point Gauss–Legendre frequency grid (5-point stencil step-converged to 7.4e-10 Ha/Bohr, and the 3-point stencil at ferric's step); 5-point finite difference of ferric's own energy; controls: RHF gradient, E_c-only gradient, all-electron vs frozen core, 6- vs 40-point gridE_c ≤ 1.5e-13 Ha; gradient ≤ 1.9e-9 Ha/Bohr vs PySCF at the same 3-point step, ≤ 5.7e-8 vs a converged 5-point FD (the 3-point truncation); sum over atoms ≤ 4.8e-91e-12 Ha (E_c); 2e-8 (same step), 2e-7 (5-point) Ha/Bohrvalidation_rpa_gradient.rs, gen_rpa_gradient.py
RI-MP2 size-extensivityH2 dimer at large separation2 × monomer2e-12 Ha1e-7 Harimp2_size_extensivity.rs
RHF/UHF/ROHF/KS gradients, including density-fitted J/Kwater, OH, HO2 / cc-pVDZ, 6-31Gfinite differences of the energy; PySCF df.grad1e-7 to 3e-7 Ha/Bohr (FD); ~1e-10 (PySCF)1e-6 Ha/Bohrdf_jk_gradient.rs
KS gradient on a custom or angularly pruned XC grid (RKS LDA/PBE, UKS PBE, ROKS PBE; flat 40x194, NWChem 30x194, SGX-pruned 20x194), through restricted_scf_gradient / unrestricted_scf_gradient / restricted_open_scf_gradientwater, NH2 / 6-31Gcentral FD of the energy on the same grid; an LDA water optimization on 40x194 then checked for a stationary point of that grid's energy0.9e-7 to 1.4e-7 Ha/Bohr (default-grid FD floor 0.9e-7 to 1.4e-7); the default-grid gradient paired with the same SCF misses by 2.8e-4 to 6.0e-4; the optimum has max FD gradient 1.4e-7. The VV10 nonlocal gradient (no Becke weight response by design) is not FD-validated, only shown to depend on nlc_grid3e-7 Ha/Bohrks_gradient_scf_grid.rs
Analytic RHF Hessian (skeleton one- and two-electron, overlap/W and CPHF response terms) and its harmonic frequenciesH2O, NH3, CH2O / cc-pVDZ; distorted H2O / def2-SVPPySCF hessian.rhf (analytic, and its skeleton partial_hess_elec + hess_nuc); PySCF finite differences of its analytic gradient; ferric's own gradient differencedtotal Hessian ≤ 1.6e-7 Ha/Bohr² vs PySCF analytic (≤ 8.2e-8 at cc-pVDZ); skeleton ≤ 5.9e-10; frequencies ≤ 6.9e-4 cm⁻¹; each skeleton term vs a finite difference of its gradient piece ≤ 3.4e-81e-6 Ha/Bohr² (total), 1e-8 (skeleton), 1e-2 cm⁻¹validation_rhf_hessian.rs, rhf_hessian_fd.rs, gen_rhf_hessian.py
Analytic RHF Hessian for RI-J with exact K (the fitted Coulomb energy ½ dᵀV⁻¹d with the Cholesky-solved metric: skeleton with the 3c and 2c second derivatives (a stencil of the first-derivative blocks) and the metric cross terms, J^x with the auxiliary-fit part in the first-order Fock matrix, the SCF's own DfJ in the CPHF kernel) and its harmonic frequenciesH2O, CH2O, distorted NH3 / cc-pVDZ with cc-pVDZ-RIFIT; distorted H2O / def2-SVP with def2-universal-jkfitPySCF has no analytic RI-J + exact-K Hessian, so: an analytic Hessian composed from PySCF's own pieces (df.hessian.rhf J skeleton and vj1 with the full auxiliary response, hessian.rhf exact K, gen_response of the only_dfj mean field) and finite differences of PySCF's analytic only_dfj gradient; ferric's own DF gradient differenced with the SCF re-converged; an exactness anchor (an auxiliary basis that spans the density's products); the exact-J Hessian as negative controltotal vs the composed PySCF Hessian ≤ 1.6e-7 Ha/Bohr² (3.3e-8 H2O, 8.3e-8 CH2O, 1.7e-8 NH3, 1.6e-7 H2O/def2-SVP); skeleton ≤ 1.9e-10; frequencies ≤ 6.9e-4 cm⁻¹; vs PySCF's FD Hessian ≤ 1.6e-6 (that FD's own floor); vs ferric's differenced DF gradient (displaced SCFs converged to 1e-14 in energy) 9.2e-11 (STO-3G), 3.3e-10 (cc-pVDZ), 1.2e-10 (NH3), 4.2e-10 (def2-SVP); with an auxiliary basis that spans the density's products the fitted skeleton and J^x equal the exact four-centre ones to 2.2e-13 and 5.8e-13 (1.8e-5 and 4.4e-5 with the ordinary RIFIT functions); the exact-J Hessian misses by 1.0e-4 (def2-SVP) to 5.3e-4 (51× to 265× the bar) and its frequencies by 0.32 to 1.04 cm⁻¹; omitting the auxiliary-fit part of J^x, the metric second-derivative term or the cross terms misses by 0.12 to 7.5e3; a stencil step ten times too large (4.0e-7) is seen only by the 1e-8 comparison with ferric's own gradient, and an explicit metric inverse is blind on well-conditioned metrics (8e-12 for water) and seen only on an ill-conditioned one (9.0e-7 on the RI-J pieces for benzene/def2-universal-jkfit); neither reaches the PySCF bar2e-6 Ha/Bohr² (total), 2e-9 (skeleton), 1e-5 (vs PySCF FD), 1e-8 (vs ferric's DF gradient), 1e-2 cm⁻¹; control ≥ 10× the bar. RI-J with an external potential, and a metric with eigenvalues below 1e-10 are refused, not validatedvalidation_df_hessian.rs, df_hessian_rij.rs, df_hessian_stencil.rs, gen_df_hessian.py
Analytic RHF Hessian for RI-K (the fitted exchange energy −¼ Σ_ij L_ij · V⁻¹ L_ij per occupied pair with the SCF's V^{-1/2}V^{-1/2} metric: skeleton with the 3c and 2c second derivatives (a stencil of the first-derivative blocks) and the cross terms, K^x with its metric and auxiliary-centre response in the first-order Fock matrix, the SCF's own DfK in the CPHF kernel), alone and with RI-J, and its harmonic frequenciesH2O, CH2O, distorted NH3 / cc-pVDZ and distorted H2O / def2-SVP, all with def2-universal-jkfit; RI-K alone also with STO-3G, 6-31G and cc-pVDZ-RIFITPySCF's analytic density-fitted Hessian (df.hessian.rhf, full auxiliary response; J and K fitted with the same auxiliary basis, a direct reference) and finite differences of PySCF's analytic DF gradient; ferric's own DF-K gradient differenced with the SCF re-converged; for one occupied column the RI-K skeleton equals 2α times the RI-J skeleton (an independent code path); the exact-integral Hessian as negative controltotal (RI-J + RI-K) vs PySCF ≤ 1.6e-7 Ha/Bohr² (3.3e-8 H2O, 8.2e-8 CH2O, 1.8e-8 NH3, 1.6e-7 H2O/def2-SVP); frequencies ≤ 6.9e-4 cm⁻¹; vs PySCF's FD Hessian ≤ 1.6e-6 (that FD's own floor); vs ferric's differenced DF-K gradient (RI-K alone, displaced SCFs converged to 1e-14 in energy) 1.1e-10 (STO-3G), 1.3e-10 (6-31G), 3.4e-10 (cc-pVDZ), 5.7e-10 (NH3), 4.8e-10 (def2-SVP) and 3.8e-10 for RI-J + RI-K at cc-pVDZ; K^x vs the SCF's DfK differenced ≤ 5e-7; the one-column identity holds to 5e-11; the error is flat (1.3e-10 to 4.4e-10) for cond(V) 2.5e5 to 2.5e7 and 6.7e-9 at 2e8, where the Hessian warns; the exact-integral Hessian misses by 2.7e-5 (def2-SVP) to 1.6e-4 (CH2O) (17× to 99× the bar) and its frequencies by 0.28 to 0.54 cm⁻¹, RI-J with exact K by 8.7e-5 to 1.6e-4, RI-K with exact J by 1.0e-4 to 3.2e-4; dropping K^x, its metric response, its auxiliary-centre part, the 2c second derivative or the cross terms misses by 2.0e-2 to 5.9e4; a stencil step ten times too large (6.8e-8) is seen only by the comparison with ferric's own gradient, and an explicit metric inverse is blind on well-conditioned metrics (4e-13 for water) and 7.1e-9 on benzene/def2-universal-jkfit; there is no exactly-spanning auxiliary basis for exchange (the pair densities of different atoms are two-centre products), so no exact-integral anchor exists1.6e-6 Ha/Bohr² (total vs PySCF), 1e-5 (vs PySCF FD), 1e-8 (vs ferric's DF-K gradient), 1e-2 cm⁻¹; control ≥ 10× the bar. Range-separated RI-K, RI with an external potential, and a metric with a mode below 1e-10 (the modes DfK drops) are refused, not validated; auto also falls back to finite differences for a mode in [1e-10, 1e-7), cond(V) above 1e8 and memory that does not fit. The PySCF comparison runs only in the weekly validation_ CI job; the per-PR tests are the finite-difference, anchor, mutation, refusal and memory tests of df_hessian_rik.rs and df_hessian_rik_memory.rs. At cond(V) = 2e8 (aug-cc-pVQZ-RIFIT) the Hessian agrees with PySCF's analytic Hessian to 2.7e-8 (2.2e-8 at cond 2.5e5) but with the differenced ferric DF-K gradient only to 6.7e-9 (the RI-J analogue 1.7e-10), which is not established as an error of the Hessianvalidation_df_hessian_rik.rs, df_hessian_rik.rs, gen_df_hessian.py
Analytic UHF Hessian for RI-J and/or RI-K (the Coulomb energy ½ dᵀV⁻¹d of the total density with the Cholesky-solved metric, and the exchange energy as one channel per spin, −½ Σ_ij L^σ_ij · V⁻¹ L^σ_ij with X_σ = C_σ,occ over the SCF's V^{-1/2}V^{-1/2} metric shared by both channels: skeleton from the summed 3c and 2c stencil weights and the per-channel cross terms, J^x of the total density and K^x per spin in the first-order Fock matrices, the SCF's DfJ on the total response density and DfK on each spin's response density in the coupled α/β CPHF), and its harmonic frequenciesNH2 (²B1), CH3 (²A₂″) and CH2 (³B1) / cc-pVDZ, NH2 / def2-SVP, all with def2-universal-jkfit; STO-3G and 6-31G (cc-pVDZ-RIFIT) for the per-term testsPySCF's analytic density-fitted UHF Hessian (df.hessian.uhf, full auxiliary response; J and K fitted with the same auxiliary basis, a direct reference) on the lowest internally stable UHF, and finite differences of PySCF's analytic DF UHF gradient; ferric's own UHF DF gradient differenced with the SCF re-converged; a closed-shell UHF equals the RHF RI-J / RI-K / RI-JK Hessian term by term; the multi-channel RI-K terms equal the sum of the one-channel (RHF-validated) terms; exact-integral, RI-J-only and RI-K-only Hessians as negative controlstotal (RI-J + RI-K) vs PySCF ≤ 1.7e-7 Ha/Bohr² (7.6e-8 NH2, 6.1e-8 CH2, 1.7e-7 CH3, 1.2e-7 NH2/def2-SVP); frequencies ≤ 3.1e-3 cm⁻¹; vs PySCF's FD Hessian ≤ 1.1e-6 (that FD's own floor, 5.6e-7 to 1.2e-6); vs ferric's differenced UHF DF gradient (SCF converged to 1e-14 in energy) 7.1e-10 (CH2), 2.8e-9 (NH2), 9.4e-10 (CH3) at cc-pVDZ, 6.6e-11 to 7.3e-10 for RI-J, RI-K and RI-JK at STO-3G and 6-31G; closed-shell limit 5.6e-17 (RI-J), 2.9e-13 (RI-K), 1.2e-13 (RI-JK); K^x of each spin vs the SCF's DfK differenced ≤ 1.2e-8; the per-spin skeleton pieces and the J + K skeleton vs fixed-density differences of the DF gradient ≤ 4.1e-7; the exact-integral Hessian misses by 5.9e-5 (NH2/def2-SVP) to 1.2e-4 (35× to 72× the bar) and its frequencies by 0.27 to 1.4 cm⁻¹, RI-J with exact K by 9.6e-5 to 1.6e-4, RI-K with exact J by 1.6e-4 to 2.9e-4; swapped spin channels (0.57 to 1.2), the closed-shell channel for each spin (0.57 to 0.87), a dropped β channel (2.0 to 2.9), the exchange of the wrong spin in the CPHF (7.8e-2 to 0.14), a dropped K^x (4.1 to 6.0), the dropped metric second derivative (0.19 to 0.29), dropped cross terms (8.7e2 to 2.6e3) all miss by more than 10⁴ bars; a stencil step ten times too large (4.8e-8 for K, 2.4e-7 for J) is seen only by the comparison with ferric's own gradient; with df_j_aux / df_k_aux unset or empty no density-fitting code runs and the Hessian is bit-identical to the exact-integral one1.7e-6 Ha/Bohr² (total vs PySCF), 1e-5 (vs PySCF FD), 1e-8 (vs ferric's DF gradient), 1e-10 (closed-shell limit), 1e-2 cm⁻¹; control ≥ 10× the bar. RI with an external potential, range-separated RI-K and a metric with a mode below 1e-10 are refused, not validated; auto also falls back to finite differences for a mode in [1e-10, 1e-7), cond(V) above 1e8 and memory that does not fit (the two spin channels and the CPHF phase are sized together and, with the raw three-index tensor, bound the measured peak allocation of the whole Hessian (0.86 to 0.99 of estimate plus raw on two shapes)). The PySCF comparison runs only in the weekly validation_ CI job; the per-PR tests are the finite-difference, anchor, mutation, refusal, dispatch and memory tests of df_hessian_uhf.rs, df_hessian_uhf_memory.rs and frequency_hessian_dispatch.rsvalidation_df_hessian_uhf.rs, df_hessian_uhf.rs, df_hessian_uhf_memory.rs, gen_df_hessian.py
Analytic ROHF Hessian for RI-J and/or RI-K (the ROHF energy is the UHF functional of the constrained (D_α, D_β): RI-J of the total density and one RI-K channel per spin with X_α = C[:, ..n_α], X_β = C[:, ..n_β] of the single orbital set; the skeleton, J^x and per-spin K^x are the UHF ones, and the commutator-form response uses the SCF's DfJ on the total response density and DfK on each spin's response density, with the spin Focks and W rebuilt through the same fitted builders) and its harmonic frequenciesNH2 (²B1), HO2 (²A″) and CH2 (³B1) / STO-3G, 6-31G and cc-pVDZ; singlet water / 6-31G for the closed-shell limit; def2-universal-jkfit and cc-pVDZ-RIFITferric's own ROHF DF gradient differenced with the SCF re-converged (Richardson, h = 2e-3 and 1e-3, SCF to 1e-14 in energy); the RHF RI-J / RI-K / RI-JK Hessian for a closed-shell ROHF (an exactness anchor, term by term); exact-integral, RI-J-only and RI-K-only Hessians as negative controls. PySCF has no density-fitted ROHF Hessian (df.hessian covers RHF, UHF, RKS and UKS), so there is no PySCF leg: the shared per-spin RI code is validated against PySCF for UHF abovevs ferric's differenced ROHF DF gradient 5.5e-11 (NH2 RI-JK), 1.6e-11 (CH2 RI-J), 7.4e-10 (CH2 RI-K), 8.5e-10 (HO2 RI-JK) at STO-3G; 2.3e-11 (NH2) and 1.4e-11 (CH2) at 6-31G; 1.1e-10 (NH2) and 2.6e-10 (CH2) at cc-pVDZ / def2-universal-jkfit; closed-shell limit 1.0e-11 (RI-J), 1.1e-11 (RI-K), 9.8e-12 (RI-JK), the same level as the exact-integral ROHF anchor (CG tolerance); the exact-integral ROHF Hessian misses the RI-JK reference by 1.6e-4, RI-J with exact K by 1.8e-4 and RI-K with exact J by 3.4e-4 (1.6e4 to 3.4e4 bars); mutants (NH2 / 6-31G, RI-JK): J from the α density only 20, swapped spin channels 0.65, the closed-shell channel for each spin 0.61, a dropped β channel 0.53, the β occupied factor taken from α (open orbitals in the β channel) 0.32, the exchange of the other spin in the CPHF 0.16, a dropped K^x 2.4, the dropped metric second derivative 0.22 (K) and 1.3 (J), dropped cross terms 2.9e2 (K) and 1.6e3 (J), the omitted auxiliary-fit part of J^x 8.3e-2, the six ROHF response defects of the exact path 1.9e-2 to 0.64 all miss by more than 6e6 bars, the exact four-centre K inside the CPHF kernel by 1.8e-5 (5.9e3 bars, the closest), and two kernel defects (Coulomb from the α response density only, exchange of the total response density per spin) make the orbital Hessian indefinite and are caught by the solver's typed error; with df_j_aux / df_k_aux unset or empty the ROHF- and UHF-exact Hessians and the UHF RI-JK Hessian are bit-identical to the commit before this change (FNV hash of all bits, NH2 / 6-31G and CH2 / STO-3G; a one-time measurement, the committed test compares unset against empty names)1e-8 Ha/Bohr² (vs ferric's DF gradient), 1e-10 (closed-shell limit), 3e-9 (mutation bar: 130× the production agreement on the mutation system, 2.3e-11; the closest mutant misses by 5.9e3 bars); control ≥ 10× the bar. External potentials, range-separated RI-K and a metric with a mode below 1e-10 are refused, not validated; auto also falls back to finite differences for a mode in [1e-10, 1e-7), cond(V) above 1e8 and memory that does not fit. Not covered: bases above cc-pVDZ, molecules above 3 atoms, states other than the doublets and triplet above (a quartet or higher, a non-aufbau or internally unstable ROHF solution gets the Hessian of that solution), a run on a first-derivative libint, and an end-to-end CLI/Python rundf_hessian_rohf.rs, frequency_hessian_dispatch.rs
Analytic closed-shell KS Hessian (LDA, GGA, and global hybrids of both) with exact or RI-J Coulomb and exact or RI-K exchange on a grid fixed in space (rks_fixed_grid_hessian, library entry point only: not reachable from auto or analytic, so no frequency run changes). The energy differentiated has its Becke grid frozen at the reference geometry (PySCF grid_response=False), so the Hessian is the XC AO-derivative skeleton (a GGA needs the third AO derivatives, ferric_integrals::ao_grid_third), the first-order XC Fock matrix and the f_xc kernel (with the σ = |∇ρ|² couplings for a GGA) in the CPHF on top of the RHF exact-J/K Hessian with the exchange scaled by the hybrid fraction a; it omits the Becke weight response that the production KS gradient carriesdistorted H2O / STO-3G, 6-31G, cc-pVDZ (SCF-level anchors on a 50x110 grid; PySCF legs on a 30x50 grid)central FD of the validated fixed-grid gradient with a fresh fixed-grid SCF at every displaced geometry (Richardson, h = 5e-4 and 2.5e-4), that gradient vs central FD of the fixed-grid energy, each XC piece vs a frozen-density difference (no SCF), and PySCF dft.RKS (LDA_X,LDA_C_VWN, HYB_LDA_XC_LDA0, PBE0, PBE, B3LYP) Hessian with grid_response=False fed ferric's own grid points and weights through mf.grids.coords / weights (gen_ks_fixed_grid_hessian.py)gradient vs FD of the energy 3.7e-10; XC skeleton vs frozen-density gradient difference 1.9e-8 (6-31G; falls 16x per halving of h, so it is the stencil's h^4 truncation) and the first-order Fock matrix vs FD of V_xc 5.6e-12; kernel response vs FD of V_xc(D) 1.9e-12; Hessian vs FD of the fixed-grid gradient 5.7e-11 (STO-3G), 1.9e-8 (6-31G), 8.1e-9 (cc-pVDZ); Hessian from the production SCF vs the injected fixed-grid SCF 4.8e-12; vs PySCF on the same grid: energy 5.9e-12 (6-31G) and 1.2e-11 (cc-pVDZ), Hessian 4.1e-8 and 9.3e-7, frequencies 7.9e-5 and 2.8e-3 cm⁻¹ (PySCF's own analytic Hessian vs central FD of its own gradient at h = 5e-4 differs by 7.4e-7 and 4.2e-6: that is the h^4 truncation of the stencil, not reference noise (ferric's FD at that step equals PySCF's FD to 7.8e-9, and halving h takes ferric's own FD from 3.3e-6 to 2.1e-7); taking PySCF's FD to h = 2.5e-4 and 1.25e-4 the cc-pVDZ gap falls to 1.2e-6 and 9.2e-7 and stops, so about 9.2e-7 of PySCF's analytic cc-pVDZ Hessian is not the derivative of PySCF's own gradient, and that is the whole of the 9.3e-7 by which ferric's analytic Hessian differs from PySCF's analytic one (ferric's analytic Hessian matches FD of its own gradient to 8.1e-9; the cause on the PySCF side is not isolated); negative control: differencing the production gradient (grid on the atoms, with weight response) misses the fixed-grid Hessian by 1.1e-3 Ha/Bohr² (1.1e4 bars) and moves a frequency by 3.0 cm⁻¹ (water / 6-31G, default grid); production-wired mutants (XC skeleton dropped 4.8e-1, f''ρρ term dropped 1.5e2, f'ρ₍₂₎ term dropped or doubled 1.5e2, f'' term of F^x dropped 8.7e-1, f' term of F^x dropped 9.1e-1, CPHF kernel dropped 2.6e-2, doubled 3.0e-2, halved 1.4e-2) miss by 1.4e5 to 1.5e9 bars; the RHF exact-J/RI Hessian bits are unchanged (FNV hash of all bits, water STO-3G and 6-31G exact, STO-3G RI-J+RI-K and RI-J, before and after the change; a one-time measurement)1e-7 Ha/Bohr² (vs FD of the gradient, ~5x the measured max), 2e-9 Ha/Bohr (gradient), 1e-9 Ha (energy vs PySCF), 5e-7 and 1e-5 Ha/Bohr² (vs PySCF's analytic Hessian: 12x the measured 4.1e-8 at 6-31G and 11x the measured 9.3e-7 at cc-pVDZ, where the 9.3e-7 is PySCF's own analytic-vs-gradient inconsistency described above, so the cc-pVDZ bar tests ferric against PySCF to PySCF's own consistency, not tighter), 1e-3 and 3e-2 cm⁻¹. GGA and hybrid GGA (PBE, PBE0, B3LYP; STO-3G and 6-31G, PBE also cc-pVDZ): gradient vs FD of the energy 3.3e-10; XC skeleton vs frozen-density gradient difference 2.9e-11 (STO-3G) and 4.2e-8 / 9.1e-8 (6-31G, PBE0 / PBE: the stencil's h^4 truncation, error ratios 38, 22, 15.5 per halving of h); first-order Fock vs FD of V_xc 3.2e-10; kernel response vs FD of V_xc(D) 2.2e-12; Hessian vs FD of the fixed-grid gradient 5.4e-11 (PBE0), 7.7e-11 (PBE), 5.1e-11 (B3LYP) at STO-3G and 4.2e-8 (PBE0, 6-31G); the energy on the injected fixed-grid SCF vs the production SCF 1.7e-11; an LDA pushed through the gradient-dependent assembly equals the LDA Hessian to 2.8e-14; vs PySCF on the same grid: energy 3.4e-12 (PBE0), 7.1e-10 (B3LYP, bar 5e-9), 1.2e-11 (PBE/cc-pVDZ), Hessian 1.0e-7, 1.0e-7 and 9.9e-7 (the cc-pVDZ number matches the pure-LDA cc-pVDZ gap, 9.3e-7; its cause is not isolated: the ρ < 1e-10 density floor ferric applies and PySCF does not was tested and moves PySCF's Hessian by only 2e-10, so it is not the cause; PySCF's own analytic-vs-FD gap at that basis, 1.2e-5, is itself noise-limited by its stencil), frequencies 1.4e-4, 1.5e-4, 4.1e-3 cm⁻¹ (bars 5e-7, 5e-7, 1e-5 Ha/Bohr²; a full-Hessian FD anchor with d functions runs per PR for LDA and PBE at cc-pVDZ); the third AO derivatives match a difference of the analytic Hessian to 1e-8 relative for every shell up to g, pure and Cartesian; ten production-wired mutants (kernel part, F ρ_xy + G·g_xy, F^x parts, gradient terms in skeleton/F^x/CPHF, exchange) miss by 2.3e4 to 1.4e9 bars. A global-hybrid LDA (HYB_LDA_XC_LDA0, a = 0.25) scales the exact exchange of the skeleton, the first-order Fock matrix and the CPHF G by a: Hessian vs central FD of the fixed-grid gradient 6.4e-11 (STO-3G), 1.4e-8 (6-31G); gradient vs FD of the energy 2.8e-10; vs PySCF on the same grid (gen_ks_fixed_grid_hessian.py, _hyb file) energy 3.8e-12 Ha, Hessian 1.3e-7 Ha/Bohr², frequencies 2.4e-4 cm⁻¹ (bar 5e-7 and 1e-3); the three exact-exchange scalings dropped or the CPHF one doubled miss the FD anchor by 5.5e5 to 1.6e6 bars. RI-J (DfJ over def2-universal-jkfit, exact K; the RI-J skeleton, J^x and CPHF Coulomb of the RHF RI-J Hessian reused unchanged; ks_rij_fixed_grid_hessian.rs, water / STO-3G and 6-31G): fixed-grid DF-J gradient vs FD of the DF-J energy 2.6e-10 (PBE/STO-3G) and 3.1e-10 (PBE0/6-31G), bar 3e-9; LDA and PBE at 6-31G also tied; Hessian vs FD of the DF-J gradient (fresh DF-J SCF per geometry) 7.7e-11 (PBE/STO-3G), 7.1e-11 (PBE0/STO-3G), 1.9e-8 (LDA/6-31G), 9.1e-8 (PBE/6-31G; bar 2e-7); DF-J vs exact-J (PBE/6-31G): |E_DF-E_exact| 3.9e-5 Ha, max|H_DF-H_exact| 1.45e-4 (mixed-unit ratio 3.7, corroboration only), shrinking with the auxiliary basis (cc-pVDZ-RI 2.4e-3 / 2.9e-4 to def2-universal-jkfit); negative control: the exact-J Hessian misses the FD of the DF-J gradient by 1.45e-4 (RI-J: 9.1e-8); the production RI-J SCF (route recorded) gives the injected-SCF Hessian to 2.3e-12; vs PySCF density_fit(only_dfj=True) Hessian (auxbasis_response = 2, grid_response=False, same grid, pure functionals only since PySCF has no RI-J + exact-K Hessian; _rij / _pbe_rij files): energy 5.9e-12 / 5.8e-12, Hessian 4.2e-8 (LDA) and 7.6e-7 (PBE; PySCF's stored raw analytic PBE DF-J Hessian is 9.1e-7 asymmetric, which limits agreement), frequencies 8e-5 and 1e-3 cm⁻¹ (bars 5e-7 / 2e-6 Ha/Bohr²; the per-PR FD anchor at 2e-7 is the discriminating test, the PySCF comparison corroboration); seven production-wired RI-J mutants (2c metric second derivative dropped 2.2, 3c-2c cross terms dropped 7.6e3, aux-fit part of J^x dropped 0.15, erfc metric 3.5e3, exact four-centre Γ_J skeleton in place of the fitted one 2.0e-4, exact J^x 1.4e-4, exact J in the CPHF 7.1e-5) miss by 3.5e2 to 3.8e10 bars; hybrid RI-J + exact K has NO PySCF reference (PySCF has no RI-J + exact-K Hessian) and is anchored by ferric's own FD only (PBE0/STO-3G Hessian, PBE0/6-31G gradient). RI-K (DfK, the hybrid's exchange −(a/4) Tr[D K_DF(D)]: the one closed-shell channel of the RHF RI-K Hessian with weight −a/4, its skeleton, K^x and CPHF DfK reused unchanged; ks_rik_fixed_grid_hessian.rs, water / STO-3G and 6-31G, def2-universal-jkfit): fixed-grid RI-K gradient vs FD of the RI-K energy 1.6e-9 (PBE0/STO-3G, RI-J), 1.3e-9 (B3LYP/6-31G, RI-J), 1.7e-9 (PBE0/6-31G, exact J), bar 3e-9; Hessian vs FD of the RI-K gradient (fresh RI-K SCF per geometry) 7.3e-11 (PBE0/STO-3G, RI-J), 8.9e-11 (PBE0/STO-3G, exact J), 5.7e-11 (B3LYP/STO-3G), 1.4e-8 (hybrid LDA/6-31G), 4.2e-8 (PBE0/6-31G, exact J and RI-J; bar 5e-7, set by the h^4 FD truncation of the GGA XC gradient: B3LYP/6-31G measures 1.2e-7 and runs only against PySCF); exactness anchor: a pure functional handed an RI-K fit gives the RI-J Hessian to 0 (bar 1e-12; the RI-K weight with the exact-exchange fraction forgotten misses by 2.9); RI-K vs exact K (PBE0/6-31G, RI-J both): |dE| 1.22e-5 / 4.31e-6 / 3.33e-6 Ha and max |dH| 2.26e-5 / 1.41e-6 / 1.53e-6 for def2-universal-jkfit / cc-pVTZ-JKFIT / cc-pVQZ-JKFIT (the Hessian difference drops 16x and then sits at the fit's floor for this orbital basis, where the energy difference also stops falling); negative control: the exact-K Hessian misses the FD of the RI-K gradient by 2.3e-5 (RI-K: 4.2e-8); the production SCF with the default RI-J and RI-K (route recorded) gives the injected-SCF Hessian to 3.3e-12; vs PySCF density_fit(auxbasis) (J and K over one def2-universal-jkfit, auxbasis_response = 2, grid_response=False, same grid; _pbe0_rijk / _b3lyp_rijk files): energy 3.9e-12 / 7.1e-10, Hessian 1.0e-7 / 1.0e-7, frequencies 1.4e-4 / 1.6e-4 cm⁻¹ (bars 1e-6 Ha/Bohr² and 2e-3 cm⁻¹); PySCF's raw analytic Hessians are 4.1e-8 (PBE0) and 1.9e-7 (B3LYP) asymmetric, so the B3LYP agreement is at PySCF's own asymmetry while the PBE0 one is 2.4x above it (the cause is not isolated), and PySCF's own analytic Hessian vs central FD of its own DF gradient at h = 5e-4 differs by 3.8e-6 (PBE0) and 1.3e-6 (B3LYP), the h^4 truncation of that stencil as for the exact-J cases; a J fit and a K fit over DIFFERENT bases (def2-universal-jkfit for J, cc-pVTZ-JKFIT for K, PBE0/STO-3G) match the FD of their gradient to 5.9e-11 (bar 1e-9), and fitting the RI-K skeleton and K^x over the J basis misses by 3.4e-5, the CPHF DfK over the J basis by 3.1e-7, and at that bar the exact Γ_K skeleton, exact K^x and exact K in the CPHF by 3.2e-6, 3.5e-6 and 1.3e-6 (3.1e2 to 3.4e4 bars); thirteen production-wired RI-K mutants (2c metric second derivative 6.8e-2, cross terms 2.8e2, metric response of K^x 3.9e-2, auxiliary-centre part of K^x 2.8e-2, transposed pair in K^x 0.14, erfc metric 2.1, exact four-centre Γ_K skeleton 3.0e-5, exact K^x 2.0e-5, exact K in the CPHF 5.2e-6, RI-K weight without a 1.6, exchange dropped from the skeleton 0.17, from F^x 0.14, from the CPHF 5.5e-2) miss by 10.4 to 1.4e9 bars (PBE0/6-31G, RI-J + RI-K). The SCF must state its fits: the same RhfConfig the SCF ran with (df_j_aux / df_k_aux unset resolve to the default basis where the SCF fits, as in solve_rhf; a pure functional never fits K, so a named df_k_aux is inert there; a stated basis must equal the recorded route's); a Fock matrix rebuilt with the stated Coulomb and exchange matrices must reproduce the stored one (an exact-K SCF stated as RI-K, or the reverse, is refused). The Becke weight response is the opt-in moving-grid variant (next row); Auto is unchanged and reaches neither. meta-GGA, VV10, double hybrids (B2PLYP, DSD-PBEP86: the MP2 term is not differentiated), range-separated hybrids (and so range-separated RI-K), open-shell references, ECPs and external potentials are refused with a typed error naming the next unit of work; a first-derivative libint2 returns a typed error. Grid caveat (independent review): this Hessian is the derivative of the fixed-grid energy, and at the default 75x110 grid it is itself about 1e-3 Ha/Bohr² from the converged-grid answer (2.1e-6 at 150x590) and breaks translational invariance by 9.7e-4, because a grid frozen in space is not translation invariant; harmonic frequencies are expected to be only weakly affected because translations and rotations are projected out, but that was not measured for this surface. The 1.1e-3 gap to differencing the production gradient closes with grid refinement: it is quadrature error plus the weight response, not a defect. The negative control runs on the default grid because the plain ks_gradient_closed entry point differentiates the default grid by design (the *_on_grids variants and restricted_scf_gradient take the SCF's grid, issue #428). Pruned grids are refused. A Fock matrix rebuilt at the result's density must reproduce its stored Fock to 1e-8 (measured 0 to 3.6e-15), which refuses an HF, other-functional or missing-external-potential result handed in under an LDA configuration (residuals 2.0, 0.17 and 0.14 Ha). Not covered: bases above cc-pVDZ, systems above 3 atoms, a run on a first-derivative libint, and any CLI or Python surfaceks_fixed_grid_hessian.rs, validation_ks_fixed_grid_hessian.rs, ks_rij_fixed_grid_hessian.rs, ks_rik_fixed_grid_hessian.rs, gen_ks_fixed_grid_hessian.py
Analytic closed-shell KS Hessian on the MOVING (production) grid (rks_moving_grid_hessian; the frequency Hessian of every closed-shell KS run under the default hessian = "auto" wherever rks_moving_grid_hessian_available accepts the system, finite differences otherwise, and of the explicit hessian = "analytic-moving-grid", CLI [frequencies] and Python run_frequencies; analytic reaches neither KS Hessian). The energy differentiated is the one the production KS SCF and ks_gradient_closed use: grid points ride their home atom and the Becke weights (original Becke, 3 iterations, Bragg radii) move with the nuclei. Per home atom h the XC skeleton is the fixed-grid AO skeleton plus the point-fixed Becke weight terms W''f + W'f'ᵀ + f'W'ᵀ (ferric_dft::becke_hessian, contracted point by point, never stored) on the atoms ≠ h, with h's rows and columns filled by translation invariance; the first-order XC Fock matrix gains V[W'_x f_ρ, 2W'_x f_σ∇ρ] and the same fill; the CPKS kernel and every non-XC term are the fixed-grid ones. LDA, GGA and global hybrids, exact or RI-J Coulomb, exact or RI-K exchangedistorted H2O / STO-3G, 6-31G, cc-pVDZ (50x110 and the default 75x110 grid); Ne / 6-31G (exactness anchor)central FD (Richardson, h = 5e-4, 2.5e-4) of the production gradient (ks_gradient_closed_with_exchange_on_grids) with a production SCF at every displaced geometry; at frozen density, central FD of ferric_dft's moving-grid XC gradient (riding weight1, an independent construction) and of V_xc on a grid rebuilt at each displaced geometry; the Becke weight Hessian vs FD of becke_weights_and_grad; the Python prototype (prototypes/ks_hessian_grid_response, second-order forward AD, H2O/STO-3G LDA: vs FD of its gradient 5.6e-8 at h = 5e-4, falling as h², and the translation-fill reconstruction 4.9e-15)exactness anchor: one atom gives a moving-grid XC skeleton and F^x of exactly 0 (fixed grid 9.4e-3 LDA, 4.6e-2 PBE; the anchor is blind to the weight terms); Becke weight Hessian vs FD 3.9e-12 (four atoms, 35 points; mutants 0.48 to 3.4); moving XC skeleton vs FD of the moving gradient 4.9e-11 and F^x vs FD of rebuilt V_xc 7.3e-12 (LDA, PBE × STO-3G, 6-31G), 2.4e-11 / 7.9e-12 (PBE cc-pVDZ), while the fixed-grid skeleton misses the same FD by 2.4e-3 to 9.6e-3; translation sum rule 6e-17 (skeleton and F^x, by construction) and 4e-12 to 3e-11 on the full Hessian; full Hessian vs FD of the production gradient 7.9e-11 (LDA) and 4.8e-11 (PBE) at STO-3G, 1.7e-10 (LDA / 6-31G, default grid: the system and reference of the fixed-grid negative control, which the fixed-grid Hessian misses by 1.1e-3 and 3.0 cm⁻¹), 1.05e-9 (PBE / 6-31G; 5.5e-10, 1.05e-9, 1.6e-10 at h = 1e-3, 5e-4, 2.5e-4, so noise, not truncation), 6.0e-11 / 8.2e-11 (PBE0 / STO-3G, 6-31G), 5.8e-11 / 6.7e-11 (RI-J PBE, RI-J + RI-K PBE0, STO-3G), 2.6e-10 (LDA / cc-pVDZ, default grid); harmonic frequencies from the moving-grid Hessian vs those of the symmetrized FD Hessian ≤ 5.0e-7 cm⁻¹ (fixed grid 2.8 to 6.7 cm⁻¹); seven mutants (drop W'' 1.1, drop the W'f' cross terms 2.1, drop the weight term of F^x 0.18, skip the translation fill 0.35, flip its sign 0.50, riding weight1 cross terms unfilled 1.2, batches not split at home atoms 9.5e-3) miss the frozen-density anchors by 9.5e6 to 3.6e9 bars (PBE / 6-31G); cost on H2O / cc-pVDZ, 75x110 grid (shared box): 2.86 s vs 2.59 s fixed-grid (LDA, ratio 1.10), 4.97 s vs 4.63 s (PBE, 1.07); a 3-atom ratio only: the Becke weight Hessian adds O(N^3) work per point and the translation fill a dense (3N)^3 product per batch, so the ratio grows with N and is unmeasured above 3 atoms. Same refusals as the fixed-grid row; a grid built from explicit points (no home atoms) is refused. Binding surface (HessianMethod::AnalyticMovingGrid, H2O / STO-3G, default 75x110 grid, RI-J): the CLI's Hessian, wavenumbers and energy (full-precision frequencies run-log record) equal harmonic_frequencies called in-process bit for bit (LDA), and Python's equal the CLI's bit for bit (PBE; validation tier, the weekly job builds ferric-cli for it); CLI moving-grid vs the CLI's own fd Richardson-extrapolated (h = 5e-4, 2.5e-4, density_conv 1e-11) 7.3e-8 (LDA) and 2.1e-7 (PBE) cm⁻¹, Hessian 2.7e-10 and 1.2e-10 Ha/Bohr² (plain FD at h/2: 1.8e-4 cm⁻¹); Python (run_frequencies(scf_density_conv=1e-11, scf_energy_conv=1e-13)) vs its own fd at the same h: 7.3e-8 and 2.1e-7 cm⁻¹, the CLI's numbers; the per-commit routing check is harmonic_frequencies(AnalyticMovingGrid) vs a direct rks_moving_grid_hessian call on the same SCF, bit for bit; auto gives the analytic-moving-grid result bit for bit for closed-shell KS (H2O / STO-3G, LDA and PBE: library, CLI and Python) and the fd result bit for bit where the moving-grid Hessian does not apply (H2 / STO-3G: meta-GGA SCAN, UKS and D3(BJ) dispersion in the CLI and Python; SCAN, UKS and a pruned grid in the library); hessian = "fd" still differences gradients for a supported KS reference; with a test-only cap on the response AO table (the one size check rks_moving_grid_hessian_available and the Hessian share), H2 / STO-3G LDA (264,000 bytes) is refused before the SCF by the explicit route and runs fd bit for bit under auto one byte below the cap, and the moving-grid Hessian at the cap; auto with the default RI-J (PBE) and RI-J + RI-K (B3LYP) fitting bases (H2O / STO-3G) passes the predicate and runs the moving-grid Hessian; a non-converged SCF under auto returns ScfConvergence; the size check dropped from the predicate fails the cap test; every refusal (Hartree-Fock, UKS, ROKS, meta-GGA, VV10, range-separated hybrid, ECP, point charges / field, pruned grid in the CLI, dispersion, and the RHF Hessian's configuration refusals: non-Coulomb operator, COSX, solvation, embedding, fractional occupations, cDFT, MOM) is named in both bindings and fires before the SCF (no SCF iteration in the run log); run_saddle refuses the value; mutants: the fixed-grid Hessian in its place misses by 4.63 (LDA) and 2.34 (PBE) cm⁻¹ in both bindings and fails the per-commit routing check, the route sent to FD fails the source check, a binding ignoring the knob fails the fd-stays-selectable check, auto routed back to FD for KS fails the auto = analytic-moving-grid check, auto sent to the moving-grid route without the support predicate fails the auto = fd fallback check, the predicate not called fails every pre-SCF refusal. Not covered: bases above cc-pVDZ, systems above 3 atoms in the full-Hessian anchor, pruned grids (refused), comparison with another code's grid-response Hessian, and the binding surface above STO-3G water1e-10 Ha/Bohr² (Becke weight Hessian); 1e-9 Ha/Bohr² (skeleton) and 1e-10 Ha/Bohr (F^x); 2e-9 Ha/Bohr² (per-commit full Hessian, STO-3G and LDA / 6-31G); 2e-8 Ha/Bohr² (validation tier: PBE / 6-31G noise, cc-pVDZ, hybrids, RI); 1e-12 (sum rule of the pieces); exactly 0 (one atom); every mutant ≥ 10 bars; bindings: bit-for-bit equality, 3e-6 cm⁻¹ and 3e-9 Ha/Bohr² (CLI vs FD), 3e-6 cm⁻¹ (Python vs FD)ks_moving_grid_frequencies.rs (CLI), validation_ks_moving_grid_frequencies.rs, test_ks_moving_grid_frequencies.py, ks_moving_grid_frequencies.rs (library), ks_moving_grid_hessian.rs, validation_ks_moving_grid_hessian.rs, becke_hessian.rs
Analytic UHF Hessian (skeleton one- and two-electron, overlap/W and coupled α/β CPHF response terms) and its harmonic frequenciesOH (²Π), NH2 (²B1), CH2 (³B1) / cc-pVDZ; tilted OH and off-C2v CH2 / STO-3G, 6-31GPySCF hessian.uhf on the lowest internally stable UHF (analytic, and its skeleton partial_hess_elec + hess_nuc); PySCF finite differences of its analytic gradient; ferric's own UHF gradient differenced; ferric's RHF Hessian for a closed-shell UHFtotal Hessian ≤ 3.8e-7 Ha/Bohr² vs PySCF analytic (OH; ≤ 7.7e-8 for NH2 and CH2); skeleton ≤ 1.4e-10; frequencies ≤ 1.4e-4 cm⁻¹; on closed-shell water the UHF Hessian equals the RHF one term by term (response 2.8e-17)2e-6 Ha/Bohr² (total), 1e-8 (skeleton), 2e-3 cm⁻¹, 1e-12 (vs RHF)validation_uhf_hessian.rs, uhf_hessian_fd.rs, gen_uhf_hessian.py
Analytic ROHF Hessian (skeleton with the ROHF energy-weighted density W = sym(D_α F_α D_α + D_α F_β D_β), plus the closed-virtual / open-virtual / closed-open orbital response in commutator form) and its harmonic frequencies; a semicanonicalized result is mapped back to ROHF orbitalsNH2 (²B1), CH2 (³B1), HO2 (²A″) / cc-pVDZ; distorted NH2, CH2, HO2 and singlet water / STO-3G, 6-31Gcentral FD of PySCF's analytic ROHF gradient (PySCF has no ROHF Hessian), h and 2h; ferric's own ROHF gradient differenced with the SCF re-converged; ferric's RHF Hessian for a closed-shell ROHF; the skeleton alone and the UHF Hessian at ROHF orbitals as negative controlstotal Hessian vs PySCF FD 6.6e-7 (NH2), 5.6e-7 (CH2), 1.0e-6 (HO2) Ha/Bohr² against PySCF's own h-vs-2h gap of 0.7e-6 to 2.6e-6; frequencies ≤ 0.0032 cm⁻¹; vs FD of ferric's gradient ≤ 3.2e-10; singlet water equals the RHF Hessian to 1.1e-11 (a real ROHF solve and a relabelled RHF result); against PySCF's FD the Hessian without the response misses by 0.35 (NH2), 0.47 (CH2), 0.83 (HO2) (frequencies by 1.3e3 to 2.5e3 cm⁻¹) and the UHF Hessian at ROHF orbitals by 7.8e-2 (NH2) and 6.0e-2 (CH2), while on HO2 that UHF Hessian refuses with a typed error (non-positive-definite CPHF); against ferric's own FD (NH2, CH2 / 6-31G, HO2 / STO-3G) the misses are 0.30, 0.36, 1.1 (no response) and 5.9e-2, 4.8e-2, 3.9e-2 (UHF at ROHF orbitals); eight response mutants each miss by ≥ 1e6 × the bar. External potentials and COSX are refused, not validated (ECPs: see the ECP Hessian row)3e-6 Ha/Bohr² (vs PySCF FD; about 3× the largest measured 1.0e-6, because PySCF's own h-vs-2h gap reaches 2.6e-6), 1e-8 (vs ferric FD), 1e-10 (singlet anchor), 0.03 cm⁻¹; control ≥ 10× the barvalidation_rohf_hessian.rs, rohf_hessian_response.rs, rohf_hessian_skeleton.rs, gen_rohf_hessian.py
Analytic RHF/UHF Hessian with external point and Gaussian-smeared charges (QM/MM, QM–QM block, charges fixed; hessian.rs, hessian_smeared.rs): charge–nucleus repulsion Hessian, charge-attraction second derivatives with the charge-centre blocks discarded, ∂V_ext/∂x in the CPHF Fock derivative; smeared charge-attraction second derivatives by a fourth-order rigid-atom stencil of the first-derivative three-centre blocksH2O + 10 charges / cc-pVDZ (RHF), CH3OH + 20 charges / def2-SVP (RHF), H2O⁺ + 10 charges / cc-pVDZ (UHF, doublet); H2O and H2O⁺ + 10 smeared charges (widths 0.7/1.1/1.6 Bohr) / cc-pVDZcentral FD (1e-3 Bohr) of PySCF's analytic qmmm.mm_charge gradient (radii= for the smeared rows), QM atoms only (PySCF has no QM/MM Hessian); the same FD without the charges as negative controlmax |analytic − FD| 3.4e-7 (H2O), 8.0e-7 (CH3OH), 5.5e-7 (H2O⁺), 3.3e-7 (H2O smeared), 5.5e-7 (H2O⁺ smeared) Ha/Bohr²; the gas-phase Hessian misses by 7.7e-3 to 1.6e-2; vs FD of ferric's own embedded gradient 5.5e-7 (RHF), 3.4e-7 (UHF); translational invariance including the QM–MM cross block 3.6e-9; smeared: vs FD of ferric's own smeared gradient 5.5e-7 (RHF), 3.4e-7 (UHF), width → 1e-3 Bohr vs the point-charge Hessian 6.3e-9 (a 1.0 Bohr width misses by 2.3e-2), cross-block row sums 1.4e-8; an empty potential is bit-identical1e-5 Ha/Bohr²; control ≥ 10× the barvalidation_qmmm_hessian.rs, qmmm_hessian_fd.rs, gen_qmmm_hessian.py
Analytic RHF/UHF Hessian in a uniform external field (hessian_field.rs; the field–nuclear energy is linear in R, so only the electronic E·r term enters: skeleton Σ D ∂²(E·r) and ∂(E·r)/∂x in the CPHF Fock derivative; libint2 has no emultipole1 derivatives, so the dipole integrals are differentiated by a fourth-order stencil on the integrals, on one- and two-atom sub-molecules)H2O (RHF) and OH (UHF, doublet) / STO-3G, field (0.02, −0.03, 0.025) a.u.fifth-order second differences of ferric's converged SCF energy (no gradient involved), and central FD of ferric's analytic gradient; zero-field Hessian as negative controlmax |analytic − energy FD| 1.0e-7 on 8 elements; max |analytic − FD(gradient)| 5.4e-7 (RHF), 3.5e-7 (UHF); zero-field Hessian misses by 1.8e-2; neutral row sums 4e-12; zero field is bit-identical1e-6 (energy FD), 5e-6 (gradient FD); control ≥ 10× the barfield_hessian.rs
Analytic RHF/UHF/ROHF Hessian with ECPs (hessian_ecp.rs: skeleton Σ D ∂²V_ECP as a masked fourth-order stencil of the first-derivative ECP blocks, on the default quadrature backend, and ∂V_ECP/∂x in the CPHF Fock derivative; shells up to f)HI, CH3I / def2-SVP (RHF); CH2I (UHF, doublet) / def2-SVP; Ag2 / aug-cc-pVDZ-PP (RHF)PySCF hessian.rhf/uhf (analytic, with ECPscalar_ipipnuc, ipnucip, ipiprinv, iprinvip), its skeleton, the ECP-only skeleton rebuilt from those integrals, a fixed-density finite difference of PySCF's analytic ECP gradient term, and a finite difference of PySCF's analytic gradient; ferric's own analytic gradient differenced; the Hessian without the ECP skeleton as negative controlECP skeleton vs the fixed-density FD of PySCF's ECP gradient term ≤ 5.95e-8 Ha/Bohr² (CH2I; HI 2.6e-10, CH3I 8.2e-9, Ag2 3.5e-10); total Hessian vs FD of PySCF's analytic gradient ≤ 4.4e-7 (Ag2 6.5e-8); vs PySCF's analytic Hessian Ag2 4.9e-9, HI 9.2e-9; at worst 3.7e-6 (CH2I), which is PySCF's own ECP second-derivative roughness (its analytic ECP skeleton misses the FD of its own gradient term by 2.5e-7 CH3I and 3.7e-6 CH2I); frequencies ≤ 7.2e-3 cm⁻¹; vs ferric's own FD of the analytic ECP gradient ≤ 3.8e-7 (RHF), 1.6e-7 (UHF); the Hessian without the ECP skeleton misses by 1.9e-2 to 3.6e-2 (frequencies by 36 to 178 cm⁻¹); a stencil step sweep |S(h) − S(2h)| 9e-12 (4.5e-9 on the libecpint backend); the Ag2 case takes about 30 minutes on a loaded box (four-centre second derivatives with f functions); two different ECP elements (H–Br–I cation / def2-SVP with a second, scaled ECP table on Br; H–Ag–I doublet / aug-cc-pVDZ-PP under --ignored, about 29 minutes): masked vs unmasked stencil 1.6e-13 and 6.3e-13, vs FD of ferric's analytic gradient 2.7e-8 and 9.2e-8, the Hessian without the skeleton misses by 3.5e-2 and 4.5e-2; ECP with point charges, Gaussian-smeared charges and a uniform field (HOI RHF; H–I–H doublet with all three): vs FD of the analytic gradient ≤ 2.5e-7, the environment terms removed misses by 1.4e-2 to 2.2e-2, an unscreened nuclear charge in the charge–nucleus Hessian by 1.3e-1 to 1.4e-1 (one-time measurement; the committed test asserts the screened-vs-bare-Z difference), an empty environment is bit-identical to none and zero-valued charges agree to 1e-10; dropping ∂V_ECP/∂x from F^x misses FD by 6.2e-2 (RHF) and 4.4e-1 (UHF); the Hessian bits of all-electron water (6-31G, def2-SVP) and NH2 are identical before and after the ECP change (FNV hash, a one-time measurement); libecpint backend, measured before the refusal existed (the entry points now refuse it, so these cannot be re-run): HOI full Hessian vs FD of its own gradient 2.3e-7 and HI, CH3I, CH2I within every PySCF bar, but Ag2 misses the ECP-skeleton bar (2.76e-6 against 5e-7; noise-dominated, roughly 1/h at small h with sign changes, plus a real libecpint second-derivative bias of about 1.5e-8 from h = 8e-3 up), so the probe refuses it; libecpint's Ag2 gradient misses PySCF by 8.0e-8 Ha/Bohr (quadrature 5.3e-10), inside the existing libecpint gradient floor, but no committed gradient anchor covers libecpint for Ag or any aug-cc-pVDZ-PP element and the finite-difference fallback under FERRIC_ECP_BACKEND=libecpint is not validated on Ag2 (estimated ~5e-7, unmeasured); the RHF Hessian bits of the all-electron systems drift between the ECP change and HEAD only because #417's incremental-Fock gate changes the SCF at tight energy_conv (6-31G water 15 to 14 iterations, E moves 1.9e-12); with FERRIC_SCF_INCREMENTAL=1 the Hessian hashes match the ECP-change commit bit for bit; ECP with density fitting and ROHF with an ECP (HOI RHF, H–I–H doublet UHF and ROHF / def2-SVP; per-commit ecp_df_hessian.rs, validation tier validation_ecp_df_hessian.rs, 264 s on two threads): vs FD of ferric's analytic fitted gradient 1.0e-7 to 3.7e-7 for RHF RI-J, RI-K and RI-JK, UHF RI-J, RI-K and RI-JK, ROHF exact, RI-J, RI-K and RI-JK (bar 1e-6, derived as 3× the floor and 10× below the smallest defect, the 1.0e-5 fitting signal); the fitted ECP gradient vs a 5-point FD of the fitted energy 3.7e-10; vs PySCF's density-fitted analytic Hessian (RHF HI, CH3I; UHF CH2I) 9.6e-9 to 2.8e-7 (bar 1e-6) and 3.7e-6 for CH2I UHF, which is PySCF's own analytic-vs-FD roughness (3.7e-6; bar twice that), and vs PySCF's FD (the only PySCF reference for ROHF) ≤ 4.4e-7; the exact-integral Hessian misses these references by 1.0e-5 (HI) to 1.1e-3 (rifit), so the fitting signal is present; the Hessian without the ECP skeleton misses FD by 1.2e-2 to 6.6e-2 and without ∂V_ECP/∂x in F^x by 8.3e-2 to 1.9, on every route; dropping the ECP skeleton from the shared RHF or the UHF-DF assembly fails the matching tests (mutation, one-time); the ROHF Fock-trust check rebuilt h without V_ECP (25 Ha deviation, typed refusal) until hcore_ecp replaced it. CONDITIONING: the ordinary def2-universal-jkfit on iodine has an RI-K metric of cond 4.9e8 (HI) to 1.4e9 (CH3I), above the code's 1e8 limit, so the probe refuses it for Auto (tested: Auto runs finite differences, 12 gradients for HI) and the jkfit comparisons are evidence AT that limit, labelled as such in the tests; the within-range evidence uses def2-svp-rifit (probe-accepted for HI, HOI, CH3I). An ECP with an external potential on the fitted routes stays refused5e-7 (ECP skeleton vs FD), 1e-5 (vs PySCF analytic), 2e-6 (vs PySCF FD), 5e-2 cm⁻¹ (5e-6 for the environment and two-element FD checks); control ≥ 10× the bar. Refused, not validated: shells above f (probe, before any SCF), the libecpint backend. Not covered: the synthetic Br table is iodine's scaled (same shells, n_core 28; the real H–Ag–I also has 28 on both), so table-structure defects and per-element n_core mix-ups are untested. A first-derivative libint is refused by the probe before any SCF; the ECP route needs only the one-body and four-centre second derivatives up to f, which the wheel's export carries, but a wheel build has not been runvalidation_ecp_hessian.rs, ecp_hessian_combinations.rs, ecp_hessian_libecpint.rs, ecp_hessian.rs, ecp_df_hessian.rs, validation_ecp_df_hessian.rs, gen_ecp_hessian.py, gen_ecp_df_hessian.py, validation_ecp_hessian_combinations.rs
Harmonic frequencies (FD of analytic gradients; Cartesian Hessian and cm⁻¹)H2O, NH3 × RHF, PBE, B3LYP / 6-31G, cc-pVDZ; UHF OH, CH3, HO2, UKS-PBE and ROHF CH3 / 6-31GPySCF same-step FD of analytic gradients (KS with grid response); PySCF analytic hessian.rhf/uhf/rks/uks; thermo.harmonic_analysis with ferric's massessame-step FD: 2.4e-7 Ha/Bohr², 7.5e-4 cm⁻¹; HF vs analytic 2.75e-5 Ha/Bohr², 0.10 cm⁻¹ (the 5e-3 Bohr step's truncation); KS vs PySCF's analytic Hessian differs by 5–35 cm⁻¹ because that Hessian has no grid response2e-6 Ha/Bohr², 5e-3 cm⁻¹ (same-step); 1e-4 Ha/Bohr², 0.5 cm⁻¹ (HF analytic)validation_frequencies.rs, gen_frequencies.py
Meta-GGA gradient, closed shell (SCAN, r2SCAN)H2O, NH3 / 6-31G, def2-SVP; RI-J (default) and exact JPySCF RKS grid_response=True, same grid and density fitting; FD of ferric's own energy≤ 1.3e-9 Ha/Bohr (def2-SVP 5.0e-10); analytic vs own FD 1.0e-91e-8 Ha/Bohrvalidation_mgga_gradients.rs, gen_mgga_gradients.py
Meta-GGA gradient, open shell (SCAN, r2SCAN)UKS: HO2, NH2 / 6-31G; ROKS: NH2 / 6-31GPySCF UKS grid_response=True + stability(); ROKS: central FD of PySCF ROKS energy; FD of ferric's own energyUKS ≤ 6.2e-9 Ha/Bohr; ROKS 2.2e-10 vs the FD; energies ≤ 5.7e-13 Ha3e-8 Ha/Bohr (UKS); 1e-7 (ROKS)validation_mgga_gradients.rs, gen_mgga_gradients.py
Meta-GGA (τ-only) fxc response (ferric_dft::fxc::MggaFxcKernel): RKS/UKS internal stability, RKS/UKS Newton, RKS/UKS/ROKS KS Z-vectorStability: NH2 (UKS), water (RKS) / 6-31G, r2SCAN, exact J, flat (75,110) Becke grid. Z-vector: water (RKS), NH2 (UKS), HCO and CH2 triplet (ROKS) / STO-3G, r2SCAN, through the MBD@rsSCS exact gradient. Kernel: closed shell, r2SCAN and SCANPySCF newton_ah.gen_g_hop_uhf / gen_g_hop_rhf dense orbital Hessians on the same grid; Newton: ferric's DIIS energy; Z-vector: central FD of the full SCF + MBD pipeline; kernel: FD of the production Vxcdense spectrum and Davidson λ_min ≤ 1.5e-10 Ha; SCF energy 5.7e-14 Ha; Newton vs DIIS ≤ 1.4e-14 Ha; Z-vector exact gradient 8.0e-10 (RKS), 1.1e-11 (UKS), 2.0e-11 (ROKS) Ha/Bohr; kernel 5.4e-9 (r2SCAN), 3.6e-7 (SCAN) relative. ROKS Newton/AH and TRAH stay on DIIS for a meta-GGA; the analytic KS Hessian refuses it1e-8 Ha (eigenvalues); 1e-9 Ha (energy, Newton); 5e-9 (RKS), 5e-10 (UKS, ROKS) Ha/Bohrmgga_response_vs_pyscf.rs, gen_mgga_response.py, mbd_scf_gradient.rs, mbd_scf_gradient_uks.rs, mbd_scf_gradient_roks.rs, unit tests in fxc.rs
COSX exchange, dense-grid limitwater / cc-pVDZdirect K3.3e-7—cosx_k_anchors.rs; see SCF: choosing how exchange is built
COSX SCF energy, (50,110)+fitwater / cc-pVDZdirect K4.9e-6 Ha—″
COSX SCF energy, (50,110)+fitbutane / def2-SVPdirect K1.7e-4 Ha—″
COSX SCF energy, (50,110)+fitbutane / def2-TZVPdirect K1.2e-4 Ha—″
COSX, open shellCH3 doublet / cc-pVDZdirect K1.96e-5 Ha (UHF), 1.97e-5 Ha (ROHF)—k_builder_open_shell.rs
COSX analytic gradient (RHF, RKS, UHF; default overlap fit for RHF/UHF)water / STO-3G, 6-31G; HO2 / STO-3Gfinite differences of the COSX energy1.6e-9 to 4.3e-9 Ha/Bohr1e-6 / 3e-8 Ha/Bohrcosx_gradient.rs
COSX analytic gradient, open shell (UKS-B3LYP, ROKS-B3LYP, ROHF; fit off; flat (50,110) and pruned sgx (35,194) grids)HO2 / STO-3G, NH2 / STO-3Gcentral FD of the COSX energy on the same grid (exchange-isolated against the direct-K run)1.7e-10 to 2.0e-9 Ha/Bohr; the exact-K gradient paired with the COSX energy misses by 6.1e-6 to 2.4e-41e-7 Ha/Bohrcosx_gradient_open_shell.rs
MBD@rsSCS gradient on a UKS-PBE0 reference with COSX exchange (fit off)NH2 / STO-3Gcentral FD of the full UKS(COSX) + MBD pipeline2.3e-11 Ha/Bohr; the exact-K Z-vector contraction misses by 4.6e-101e-10 Ha/Bohrmbd_scf_gradient_uks.rs
COSX exchange against PySCF and against exact K: fit off with no screening on the identical grid; angular-grid convergence (75,110)→(75,590) with production knobs; analytic gradient with fit on (RHF) and off (B3LYP)water / aug-cc-pVDZ (RHF, B3LYP); butane / def2-SVP (RHF); distorted water, NH3 for gradientsPySCF sgx on ferric's grid recipe (exact J, fit off, no screening); ferric exact-K energy; central FD of the COSX energysame grid ≤ 1.2e-12 Ha at every grid; error shrinks 93x (butane) to 2576x (water) from 110 to 590 points; exact-K vs PySCF ≤ 2.1e-11 Ha; gradient ≤ 1.6e-8 Ha/Bohr1e-10 Ha; ≥ 30x; 1e-9 Ha; 2e-7 Ha/Bohrvalidation_cosx.rs, gen_cosx.py
RIJCOSX energy (RI-J + COSX K): separation into RI-J error + COSX errorwater / 6-31G (HF, B3LYP); HO2 / 6-31G (UHF)the three other corners of (exact or RI) J × (exact or COSX) K, same gridsecond-order cross term, 2.8–5.8 × e_cosx·e_rij (7.8e-10 Ha at (50,110), 2.3e-11 Ha at (75,302))30 × e_cosx·e_rij, or 1e-10 Hacosx_rijcosx.rs
RIJCOSX analytic gradient (RI-J derivative + COSX derivative; flat and pruned grids; fit on and off)water / STO-3G (RHF, B3LYP); HO2 / STO-3G (UHF)central FD of the RIJCOSX energy1.7e-9 Ha/Bohr (RHF), 4.9e-10 (B3LYP, exchange-isolated), 3.6e-9 (UHF)1e-7 / 3e-8 Ha/Bohr″
Pruned COSX grid (prune = "sgx", peaks 194 and 302) against PySCF SGX with sgx_prune on the same radial grid and Becke partition, fit off, no screeningwater / aug-cc-pVDZ; butane / def2-SVP (RHF)PySCF 2.13.1≤ 1.8e-12 Ha, identical point counts1e-10 Havalidation_cosx.rs, gen_cosx_pruned.py
COSX final-grid passwater / STO-3G, 6-31G; HO2 / STO-3Gthe SCF-grid energy (final grid = SCF grid); SCF converged on the final gridbit-identical; within 1.5e-7 Ha on eight molecules (site/src/methods/scf.md)exact; —cosx_rijcosx.rs, cosx_grid_sweep.py
Lebedev 194-point ruleunit sphereexact monomial integrals; PySCF's ruleexact through degree 23; nodes and weights identical to PySCF's1e-12 relativelebedev.rs
Periodic closed-shell range-separated hybrids at the Gamma point (HSE06, CAM-B3LYP; energy only), dense AFT and RS-GDFH2 / STO-3G in a 4 Bohr cube and a triclinic cell, exxdiv ewald and nonePySCF 2.13.1 Gamma pbc.dft.RKS + AFTDF, UniformGrids 41³, cell.precision 1e-11 (8 pins)≤ 6.2e-13 Ha vs PySCF; Gamma = 1×1×1 k-mesh to 2.7e-15; RS-GDF − dense equals the plain-Coulomb fit error × 1.00–1.03; the attenuated-K fit as a matrix: 4.4e-14 / 2.1e-13 (cc-pVDZ-RI / def2-universal-jkfit)1e-10; 1e-10; 0.4 × fit error; 1e-11. Mutants moved E by ≥ 3.1e-4 (ω ×1.01, attenuated K dropped, c_sr/c_lr swapped, c_sr ×0.99, wrong exxdiv); a 1e-3 relative ω error in the fit alone moves the K matrix by 7.1e-6. Limits: the attenuated K is the k = 0 point of the k-point exchange, so the 1×1×1 agreement is not independent of the kernel (PySCF is); at a = 4 Bohr HSE06's erf piece is empty (ω = 0.11 < the smallest G), so only CAM-B3LYP exercises the attenuated fit; no range_split, and the nuclear force and the stress are the dense-AFT validation rows below (closed-shell force and stress, also reachable from the CLI and Python, then the open-shell UKS and ROKS force rows and the open-shell stress row) and, on k-point RS-GDF J/K, the closed-shell k-point RS-GDF row after them (the Gamma routes on an RS-GDF exchange have their own Gamma RS-GDF row); closed shell only (open-shell UKS is the next row); fixtures are H2/STO-3G onlypbc_grsh.rs, gen_pyscf_pbc_grsh_refs.py
Periodic closed-shell range-separated hybrids on a k-point mesh (HSE06, CAM-B3LYP, wB97X; energy only), dense AFT and RS-GDFH2 / STO-3G, cubic and triclinic cells, meshes up to 2×2×2, exxdiv ewald and nonePySCF 2.13 pbc.dft.KRKS + AFTDF; k-mesh against the explicit supercell≤ 5e-13 Ha vs the PySCF pins; k-mesh = supercell / N to 4.4e-141e-10; 1e-8 (RS-GDF)pbc_krsh_more.rs, pbc_krsh_rsgdf.rs, validation_pbc_krsh_rsgdf.rs
Periodic closed-shell range-separated hybrid nuclear forces (HSE06, CAM-B3LYP; dense-AFT J/K; kxcgrad::kpoint_rks_gradient on a k-mesh, gamma_rsh::gamma_rks_rsh_gradient at the Gamma point through the 1×1×1 mesh)H2 / STO-3G in a 4 Bohr cube, meshes 1×1×1 and 1×1×3 (non-TRIM), exxdiv ewald and none, an explicit 3-cell supercellcentral FD of ferric's own energy (fresh SCF per displacement, h = 1e-4); c_sr = c_lr = c against the global hybrid with fraction c (two SCFs, zero XC); 1×1×3 mesh against the explicit supercell; Gamma against the 1×1×1 mesh. No PySCF force pinanalytic − FD ≤ 2.9e-9 Ha/Bohr (the h² floor: 1.16e-8 → 2.89e-9 → 7.14e-10 for h = 2e-4 → 1e-4 → 5e-5); ewald − none force ≤ 7.6e-13; equal fractions − global hybrid 5.6e-17; mesh − supercell ≤ 1.1e-14; Gamma − 1×1×1 ≤ 1.1e-161e-7 (FD); 1e-9 (anchors). Mutants (1×1×3 ewald, CAM-B3LYP / HSE06) miss the FD-validated force by 1.48e-1 / 1.64e-1 (kernel factor dropped), 1.01e-1 / 9.0e-2 (factor on J too), 3.0e-2 / 5.4e-2 (c_sr/c_lr swapped), 3.4e-2 / 5.3e-2 (plain instead of mixed Madelung in the M-term), 5.2e-4 / 1.45e-5 (ω × 1.01), 3.8e-2 / 1.2e-4 (erf piece dropped), 2.8e-2 / 5.1e-3 (Madelung M-term dropped). Limits: the largest erf weight on the 1×1×3 K set is 0.245 for CAM-B3LYP and 8.7e-4 for HSE06, but HSE06's derivative still resolves its erf piece (dropping it misses by 1.2e-4, 1200 FD bars; ω × 1.01 by 145 bars); wB97X is accepted but not exercised by a force test by the library (the Python and CLI derivative gate refuses it by name); the Gamma check is glue only (the kernel is shared with the k code); the stress is the next row, the open-shell UKS and ROKS forces the two rows after it, closed-shell RS-GDF J/K the row after those; H/STO-3G fixtures only (no p or d shells). Bindings (#498): the CLI task = "optimize" (k-mesh HSE06, Gamma CAM-B3LYP) logs the returned geometry and gradient in run_end, equal to the library at that geometry bit for bit (energy and force); Python run_rks_kpts / run_rks_gamma with_gradient vs the same library numbers: energy 0, force ≤ 5.6e-17 (bar 1e-12); Python k HSE06 force vs FD of its own energies 2.8e-9 (bar 2e-8); Python Gamma vs 1×1×1 force 1.1e-16, stress 1.7e-17 (bar 1e-10)periodic_rsh_open_mgga_derivs.rs, test_pbc_rsh_open_mgga_derivs.py, pbc_krsh_force.rs, validation_krsh_force.rs, validation_pbc_kxcgrad_hybrid.rs
Periodic closed-shell range-separated hybrid stress (HSE06, CAM-B3LYP; dense-AFT J/K; kxcstress::kpoint_rks_stress on a k-mesh, gamma_rsh::gamma_rks_rsh_stress at the Gamma point through the 1×1×1 mesh)H2 / STO-3G in a 4 Bohr cube, meshes 1×1×1 and 1×1×3 (non-TRIM), exxdiv ewald and none, an explicit 3-cell supercell; the Madelung strains also on a triclinic latticecentral FD of ferric's own energy under each of the nine strain components (fresh SCF per strained cell, fixed fractional coordinates, h = 2.5e-5, shared grid cutoff); c_sr = c_lr = c against the global hybrid with fraction c (two SCFs, zero XC); 1×1×3 mesh against the explicit supercell; Gamma against the 1×1×1 mesh; ewald − none against −dv_M/dε from FD of the mixed Madelung constant on strained supercells (the density does not depend on the exxdiv, so E_ewald − E_none = −v_M per occupied orbital); dv_M^LR/dε and the mixed dv_M/dε against FD of the constants (prototyped in Python first). No PySCF pin (PySCF has no hybrid stress)analytic − FD ≤ 5.6e-9 Ha per cell over all nine components, both functionals, both exxdiv (the worst is always ε_xx; every other component ≤ 1.3e-9); ε_zz residual 1.06e-8 → 2.40e-9 → 7.2e-10 for h = 1e-4 → 5e-5 → 2.5e-5 (h²); equal fractions − global hybrid 2.2e-16; mesh − supercell σ ≤ 3.5e-15; Gamma − 1×1×1 ≤ 7.8e-16; ewald − none vs −dv_M/dε 2.4e-9; Madelung strains vs FD 2.4e-9 (LR, h = 1e-4) and ≤ 6.3e-10 (mixed)3e-8 (FD); 1e-9 / 1e-10 (anchors); 1e-8 (exxdiv); 1e-7 (Madelung). Mutants (1×1×3 ewald, CAM-B3LYP / HSE06) miss the FD-validated stress by 1.17e-1 / 3.45e-3 (f′(K²) slope term dropped), 6.0e-2 / 5.8e-2 (kernel on J too), 1.03e-1 / 1.52e-1 (c_sr/c_lr swapped), 3.44e-1 / 5.94e-1 (plain instead of mixed Madelung strain), 2.92e-1 / 5.10e-3 (v_M^LR strain dropped); the primitive-cell Madelung strain misses the supercell by 6.2e-3. Limits: ε_xx has a discrete energy step at h ≥ 1e-4 (2.1e-5 at h = 2e-4) from the atom grid's hard neighbour_cutoff (#495), which the global hybrid PBE0 shows identically, so FD runs at 2.5e-5 (where the ε_xx residual is FD-floor noise, not h²); wB97X is accepted but not exercised by the library (the Python and CLI derivative gate refuses it by name); open shell (UKS, ROKS) is the open-shell RSH stress row four rows below (closed-shell RS-GDF J/K: three rows below); bindings (#498): the CLI [cell] stress = true HSE06 tensor (H2, 1×1×2 Gamma-centred and Monkhorst-Pack) equals the library bit for bit in periodic_kpoint_stress.rs, Python with_stress runs the same library call; H/STO-3G fixtures only (no p or d shells); the Gamma check is glue only (the kernel is shared with the k code)pbc_krsh_stress.rs, validation_krsh_stress.rs
Periodic open-shell (UKS) range-separated hybrid nuclear forces (HSE06, CAM-B3LYP; dense-AFT J/K; kuxcgrad::kpoint_uks_gradient on a k-mesh, gamma_rsh::gamma_uks_rsh_gradient at the Gamma point through the 1×1×1 mesh): K_σ = K[D_σ] with exchange scale 1, both spin exchange walks weighted by the kernel factor, J bare, the mixed Madelung constant in the per-spin M-term Σ_σ D_σ S D_σH3 doublet and H4 triplet / STO-3G in the triclinic cell of the UKS energy row (polarized), meshes 1×1×1 and 1×1×2, exxdiv ewald and none; H2 singlet (4 Bohr cube, 1×1×3); an explicit 2-cell supercell (multiplicity 3)singlet k-UKS against the k-RKS RSH force (separate SCFs, per part); central FD of ferric's own polarized energy (fresh SCF per displacement, every component of both cells, both functionals, h sweep); c_sr = c_lr = c against the global-hybrid UKS force with fraction c (two SCFs, zero XC, polarized H3); 1×1×2 mesh against the supercell; Gamma against the 1×1×1 mesh; ewald against none. No PySCF force pinsinglet UKS − RKS ≤ 2.3e-13 per part; analytic − FD ≤ 3.8e-9 (H3) / 4.7e-9 (H4 triplet) Ha/Bohr at h = 1e-4 (CAM-B3LYP H3 (0,2): 1.50e-8 → 3.82e-9 → 9.16e-10 for h = 2e-4 → 1e-4 → 5e-5, h²); equal fractions − global hybrid 1.7e-16; mesh − supercell ≤ 7.2e-15; Gamma − 1×1×1 ≤ 8.5e-14; ewald − none ≤ 5.6e-141e-7 (FD); 1e-9 (anchors). Mutants on the polarized H3 doublet (1×1×2 ewald, CAM-B3LYP / HSE06) miss the FD-validated force by 2.6e-3 / 2.8e-3 (α density in the β exchange walk), 2.0e-2 / 1.6e-2 (β exchange dropped), 1.2e-2 / 3.9e-3 (M-term on the total density), 2.7e-2 / 1.1e-2 (α K in the β Fock), 1.3e-1 / 1.2e-1 (kernel factor on J), 4.3e-2 / 7.2e-2 (plain instead of mixed Madelung), 3.6e-2 / 3.3e-2 (c_sr/c_lr swapped), 9.6e-2 / 1.0e-1 (kernel factor dropped), 1.3e-2 / 2.7e-6 (erf piece dropped), 2.7e-4 / 5.1e-7 (ω × 1.01); the first four (spin bookkeeping) are 0 on the singlet. Limits: the equal-fraction anchor shares the M-term density sandwich Σ_σ D_σ S D_σ with its global-hybrid side, so it cannot see an M-term spin error (only FD does: a cross-spin M-term mutant misses FD by 2.3e-2); the largest erf weight on the H3 1×1×2 K set is 0.172 for CAM-B3LYP and 3.6e-5 for HSE06, so HSE06's erf and ω defects are resolved only at 27 and 5 FD bars on this cell (CAM-B3LYP carries the erf assertion); wB97X accepted, not force-tested; RS-GDF is the open-shell RS-GDF row below; the open-shell stress is its own row below (ROKS force: the next row); the bindings of all of them are the open-shell RSH bindings row below; bindings (#498): the CLI task = "optimize" (k-mesh HSE06 H3 1×1×2, Gamma CAM-B3LYP H3) logs the returned geometry and gradient in run_end, equal to the library at that geometry bit for bit (energy and force; PBE0 / HSE06 siblings differ by 4.2e-4 / 3.6e-3 Ha/Bohr); Python run_uks_kpts / run_uks_gamma with_gradient vs the same library numbers: energy ≤ 8.9e-16, force ≤ 1.0e-15 (bar 1e-12); Python k HSE06 UKS force vs FD of its own energies 4.1e-10 (bar 2e-8); Python Gamma vs 1×1×1 UKS CAM-B3LYP force 3.7e-12 (bar 1e-10); the binding gate (kxcgrad::check_deriv_functional with Deriv::Force / Deriv::Stress, dft::check_gamma_uks_request) refuses by name wB97X (the UKS stress, the UKS force on RS-GDF and the ROKS range-separated derivatives refused here at the time are wired since, the open-shell RSH bindings row below) (periodic_rsh_open_mgga_derivs.rs, test_pbc_rsh_open_mgga_derivs.py); H/STO-3G fixtures only (no p or d shells)pbc_kuks_rsh_force.rs, validation_kuks_rsh_force.rs, validation_periodic_rsh_open_mgga_derivs.rs, validation_pbc_kuxcgrad.rs
Periodic restricted open-shell (ROKS) range-separated hybrid nuclear forces (HSE06, CAM-B3LYP; dense-AFT J/K; kuxcgrad::kpoint_roks_gradient on a k-mesh, gamma_rsh::gamma_roks_rsh_gradient at the Gamma point through the 1×1×1 mesh): the UKS RSH assembly of the row above on the ROHF spin densities, with W_RO and the ROHF orbital-gradient gate; the routed Gamma force's Fock guard compares the rebuilt spin Focks with the Gamma result's stored spin Focks (rohf_spin_focks; the Roothaan F_eff only as a fallback for a result without them)H3 doublet and H4 triplet / STO-3G in the triclinic cell of the UKS row, meshes 1×1×1 and 1×1×2, exxdiv ewald and none; H2 singlet (4 Bohr cube, 1×1×3); an explicit 2-cell supercell (high-spin triplet)singlet k-ROKS against the k-RKS RSH force (separate SCFs, per part); central FD of ferric's own ROKS energy (fresh SCF per displacement, every component of both cells, both functionals, h sweep; PySCF has no periodic ROKS gradient); c_sr = c_lr = c against the global-hybrid ROKS force with fraction c (two zero-XC ROKS SCFs); 1×1×2 mesh against the supercell; Gamma against the 1×1×1 mesh; ewald against nonesinglet ROKS − RKS ≤ 1.2e-13 per part; analytic − FD ≤ 4.5e-9 (H3) / 4.6e-9 (H4 triplet) Ha/Bohr at h = 1e-4 (CAM-B3LYP H3 (0,2): 1.79e-8 → 4.51e-9 → 1.11e-9 for h = 2e-4 → 1e-4 → 5e-5, h²); equal fractions − global hybrid ≤ 3.4e-16; mesh − supercell ≤ 4.2e-15; Gamma − 1×1×1 ≤ 5.1e-15 (spin-Fock guard ≤ 1.0e-15); the Gamma guard refuses another grid, exxdiv or functional and an equal-and-opposite spin-Fock tamper (whose diagonal-block part F_eff cannot see); the F_eff fallback gives the identical force; ewald − none ≤ 5.8e-161e-7 (FD); 1e-9 (anchors). Mutants on the H3 doublet (1×1×2 ewald, CAM-B3LYP / HSE06) miss the FD-validated force by 3.1e-3 / 2.6e-3 (α density in the β exchange walk), 2.2e-2 / 1.8e-2 (β exchange dropped), 1.2e-2 / 3.8e-3 (M-term on the total density), 2.8e-2 / 1.2e-2 (α K in the β Fock), 1.3e-1 / 1.2e-1 (kernel factor on J), 4.4e-2 / 7.3e-2 (plain Madelung), 3.7e-2 / 3.5e-2 (c_sr/c_lr swapped), 9.9e-2 / 1.0e-1 (kernel factor dropped), 1.4e-2 / 2.8e-6 (erf piece dropped), 2.8e-4 / 5.3e-7 (ω × 1.01), 8.5e-2 / 7.2e-2 (W_RO cross term dropped); the first four are 0 on the singlet. The unrestricted W = Σ_σ D_σ F_σ D_σ is an identity at convergence (1.1e-14, H4 triplet); on a loose H4 triplet state (orbital gradient 3.6e-5) the gate refuses it; with the gate off neither form is right there (both miss the converged force, by 1.0e-5 and 1.5e-5) and the two differ by 4.9e-6, so the gate's refusal is the protection. Limits: the H3 doublet is inversion-symmetric, so its closed-open block (and the W_RO − unrestricted-W difference) vanishes for any state, hence the H4 triplet for that test; the equal-fraction anchor shares the M-term sandwich and W_RO with its global-hybrid side (spin errors in them are seen by FD only); HSE06's erf and ω defects are resolved at only 28 and 5 FD bars on this cell (asserted instead on the singlet, 1.2e-4 / 1.5e-5; CAM-B3LYP carries the open-shell erf assertion); wB97X accepted, not force-tested; RS-GDF is the open-shell RS-GDF row below; the open-shell stress is its own row below; a Gamma ROKS on a caller-supplied grid is not covered; wired to Python and the CLI by #498 (the open-shell RSH bindings row below); H/STO-3G fixtures onlypbc_kroks_rsh_force.rs, validation_kroks_rsh_force.rs
Periodic closed-shell range-separated hybrid nuclear forces and stress on k-point RS-GDF J/K (HSE06, CAM-B3LYP; kxcgrad::kpoint_rks_gradient and kxcstress::kpoint_rks_stress on a KRsGdf::build_rsh source). The plain fit's exchange pieces are scaled by c_sr, the attenuated fit of erf(ωr)/r is differentiated by the plain fit's LR and metric passes at ω on |K| ≤ 2ω√ln(1/ε) (no SR walks, no c0 G = 0 terms; a q class the build left empty contributes zero) scaled by c_lr − c_sr, and the M-term and Madelung strain carry the SCF's mixed constant (prototyped in Python first)H2 / STO-3G with an even-tempered s,p aux on the atoms, 1×1×2 mesh, RS-GDF split ω₀ = 1: a triclinic cell (CAM-type ω = 0.33) and a 6.5 Bohr cube (HSE-type ω = 0.11, where q = 0 carries K vectors); KS rows in a 4 Bohr cubeexactness anchor: the attenuated fit at ω = 1e7 (cut 24) against the plain fit, fixed density; the attenuated fit alone against central FD of its fixed-density energy; the zero-XC range-separated exchange energy (no grid) and the KS energy, central FD with fresh build and SCF per displacement (forces) and per strained cell (nine stress components); RS-GDF against the dense-AFT RSH force on the same cell, scaled by the plain-Coulomb (PBE0) fit gap. No PySCF force pinanchor: energy 1.4e-15, force 6.6e-14; attenuated alone (lindep 1e-6) 5.8e-9 / 5.2e-11 / 9.0e-12 at h = 1e-3 / 1e-4 / 1e-5; zero-XC force 1.11e-8 / 2.78e-9 / 6.6e-10 (CAM-type) and 9.7e-9 / 2.44e-9 / 6.25e-10 (HSE-type) at h = 2e-4 / 1e-4 / 5e-5 (h²); KS force 2.15e-9 / 5.8e-10 at h = 1e-4 / 5e-5 (both functionals, the same as dense AFT); zero-XC stress 1.26e-8 / 3.2e-9 (CAM-type) and 8.1e-9 / 2.1e-9 (HSE-type) at h = 1e-4 / 5e-5; Σ F ≤ 1.2e-15; RS-GDF − dense force 1.60e-5 to 1.79e-5, within 5% of the PBE0 gap of the same aux and mesh1e-11 (anchor); 1e-9 (attenuated alone, h = 1e-4); 1e-8 (zero-XC force, h = 5e-5) and 2e-8 (KS force); 3e-8 (stress); 1.5 × the PBE0 gap. Mutants (CAM-type / HSE-type) miss the FD-validated force by 2.8e-2 / 6.1e-2 (c0 kept in the attenuated derivative), 2.5e-2 / 8.4e-4 (c_lr − c_sr dropped), 4.8e-2 / 1.3e-2 (c_sr v_M only), 2.2e-2 / 1.6e-2 (c_lr v_M), 6.3e-2 / 1.2e-2 (mixed v_M scaled again by c_sr), 7.6e-2 / 9.9e-2 (plain v_M), 7.7e-2 / 1.6e-2 (no Madelung M-term), and the stress by 1.3e-1 / 1.6e-2 (scale dropped), 1.1e-1 / 3.2e-3 (attenuated strain dropped), 2.5e-1 / 2.5e-1 (plain Madelung strain), 1.6e-1 / 4.2e-3 (LR Madelung strain dropped); a derivative at the Ewald split ω₀ is refused by the rebuilt-fit check. Conditioning: the fit derivative applies the metric inverse in its eigenbasis, so kept eigenvalues just above the default lindep 1e-10 do not degrade the force: at fixed density a rigid translation of 1e-3 moves the CAM-type attenuated force by 3.6e-11 (lindep 1e-10; the energy by ≤ 1.1e-16), the attenuated fit alone matches FD at 5.8e-9 / 6.8e-11 / 7.8e-12 for h = 1e-3 / 1e-4 / 1e-5 at lindep 1e-10, the CAM-type zero-XC force at lindep 1e-10 shows the same h² truncation as at 1e-6 (1.11e-8 / 2.74e-9 / 6.2e-10), and with the #512 control (a near-duplicate aux shell whose 1.4e-11 eigenvalue is dropped at 1e-10) the attenuated fit's translation drift falls from 2.1e-8 to 4.9e-11 and its flat 3.9e-8 FD miss becomes the h² truncation (5.7e-9 / 1.9e-11 at h = 1e-3 / 1e-4), while its exchange-only plain fit was already clean (4.8e-14; the report's ~4e-6 plain-fit miss is not reproduced on that path). The ≤ 1e-10 translation target of #512 is not met when a near-duplicate eigenvalue is kept just above the cut (1.3e-10): the plain and attenuated forces still move by 1.29e-10 / 1.79e-10 (per commit, bar 2e-9; 9.2e-7 / 1.05e-7 with the explicit metric inverse). The energy itself moves by 5.4e-11 under that translation and FD of the plain fit grows as 1/h (6.1e-8 / 5.5e-7 / 4.9e-6 at h = 1e-3 / 1e-4 / 1e-5), so the residual is attributed, provisionally, to integral roundoff in the near-singular fit (shown only in an exact-integral Python model). The HSE06 KS row on the 4 Bohr cube is wiring only (erf weights ~3e-6, q = 0 empty). The atom grid adds FD steps on the triclinic and 6.5 Bohr cells that are identical on dense AFT, hence the zero-XC FD rows; the Gamma route (gamma_rks_rsh_gradient) differentiates an RS-GDF exchange too (the Gamma RS-GDF row below); open shell (the UKS and ROKS forces on RS-GDF) is the next row (the open-shell stress is the row after it; both wired, the open-shell RSH bindings row); bindings (#498): the CLI task = "optimize" CAM-B3LYP force (H2, 1×1×2, cc-pVDZ-RI) and [cell] stress = true CAM-B3LYP tensor equal the library bit for bit (B3LYP sibling on the same fit 5.7e-4 Ha/Bohr force, 5.7e-4 stress; the dense-AFT CAM-B3LYP stress differs by 2.8e-6), Python run_rks_kpts(jk="rsgdf") with_gradient / with_stress match the CLI values (differences 0 on the measuring machine, not a cross-platform guarantee: the Python test bar for the RS-GDF force and stress is 1e-6 because a different OpenBLAS kernel moves them by up to 1.25e-8 and 1.65e-9; the energy and the dense rows keep 1e-12); the RS-GDF range-separated stress is roundoff-limited: a ~1e-17 Å change in the xyz digits of the same geometry moves σ_zz by 2.8e-9, so the Python anchor reads the same xyz text as the CLI run (periodic_kpoint_stress.rs, test_pbc_rsh_open_mgga_derivs.py); H/STO-3G fixtures onlypbc_krsh_rsgdf_force.rs, pbc_kfit_force_conditioning.rs, validation_krsh_rsgdf_force.rs, rsgdf_rsh_force_proto.md
Periodic open-shell (UKS, ROKS) range-separated hybrid nuclear forces on k-point RS-GDF J/K (HSE06, CAM-B3LYP; kuxcgrad::kpoint_uks_gradient / kpoint_roks_gradient on a KRsGdf::build_rsh source). A composition of the closed-shell RS-GDF RSH force (plain fit × c_sr, attenuated fit × (c_lr − c_sr), mixed M-term) with the open-shell exchange terms (1, D_α), (1, D_β) (twice the closed-shell (½, D) per spin); no new derivativeH3 doublet / STO-3G, ET-sp aux, 1×1×2: triclinic TRI_A (CAM-type ω = 0.33, CAM-B3LYP, HSE06) and a 6.5 Bohr cube (HSE-type ω = 0.11); H2 singlet TRI2Singlet UKS ≡ #514's RKS RS-GDF force (separate SCFs) per part; c_sr = c_lr ≡ the global hybrid on the plain fit; central FD of ferric's own polarized energy (fresh build + SCF per point); RS-GDF − dense-AFT UKS force gap vs the PBE0 (plain-fit) gap. No PySCF oracle: PySCF 2.13 periodic KS gradients are FFTDF-onlySinglet: exactly 0 (bitwise D_σ = D/2; mutants move it 3.1e-2). Equal fractions: ≤ 3.6e-14 (UKS, ROKS; ewald and none). Zero-XC FD, all 9 components: 9.7e-10 / 1.0e-9 (CAM-type UKS / ROKS), 7.1e-10 (HSE-type) at h = 5e-5, 3.9× per halving. KS FD (3 components): ≤ 1.1e-9 at h = 5e-5. Gap: CAM-B3LYP 1.26e-5, HSE06 1.08e-5 vs PBE0 1.08e-51e-10; 1e-10; 1e-8 (zero XC) / 2e-8 (KS) at h = 5e-5 with a 3–6 h² ratio; 1.5 × the PBE0 gap. Mutants (spin factor, dropped / swapped β term, total-density M-term, Pulay K swap, attenuated scale and c0, Madelung constants, ROKS W cross term) miss by 3.5e-4 – 4.3e-2; source mutants of KSpins::exch_terms (½ per spin, β dropped) fail singlet and FD. Limits: HSE06's attenuated piece is nearly dead on TRI_A (2.7e-6), covered by the HSE-type cube rows; the open-shell stress is the next row; Python / CLI wire the RS-GDF open-shell RSH force (#498, the open-shell RSH bindings row); H / STO-3G onlypbc_kuks_rsh_rsgdf_force.rs, validation_kuks_rsh_rsgdf_force.rs
Periodic open-shell (UKS, ROKS) range-separated hybrid stress (HSE06, CAM-B3LYP; dense-AFT and k-point RS-GDF J/K; kuxcstress::kpoint_uks_stress / kpoint_roks_stress on a k-mesh, gamma_rsh::gamma_uks_rsh_stress at the Gamma point through the 1×1×1 mesh). A composition, no new derivative algebra: the open-shell stress assembly (exchange terms (1, D_α), (1, D_β), M-term Σ_σ D_σ S D_σ, W or W_RO) with the closed-shell RSH stress's attenuated kernel slope (dense), the plain fit × c_sr plus the attenuated fit's LR strain × (c_lr − c_sr) (RS-GDF), and the mixed Madelung constant and strain; exchange scale 1H3 doublet / STO-3G in the triclinic TRI_A (polarized) and a 6.5 Bohr cube (HSE-type ω = 0.11), H2 singlet in TRI_A; 1×1×2 mesh, RS-GDF ET-sp aux with ω₀ = 1 and lindep 1e-6 (dense on the same basis, mesh and hcore); exxdiv ewald and none; an explicit 2-cell supercell (triplet); KS on an SSF (20, 50) atom grid with an explicit neighbour cutoffsinglet UKS against the RKS RSH stress (independent SCFs and entries, per part; zero XC and KS); c_sr = c_lr = c against the global-hybrid open-shell stress with fraction c (zero XC, polarized, UKS and ROKS, both J/K, both exxdiv); central FD of ferric's own polarized energy under strain (fresh build and SCF per strained cell, h = 1e-4 and 5e-5 and the Richardson combination (4 FD(h/2) − FD(h))/3): zero XC on all nine components (CAM-type ω = 0.33, both J/K, UKS and ROKS, ewald; UKS also under none) and three components of the HSE-type cube, and the KS HSE06 / CAM-B3LYP energy on three components (UKS and ROKS, both J/K); Gamma against the 1×1×1 mesh; 1×1×2 mesh against the supercell. No PySCF pin: PySCF 2.13.1 pbc/grad/krks_stress.py / kuks_stress.py raise NotImplementedError for hybrids, and it has no RS-GDF derivativesinglet UKS − RKS: exactly 0 per part (zero XC, both J/K; audited: the singlet UKS SCF lands on D_σ = D/2 and the two term lists differ by power-of-two scalings), KS ≤ 2.8e-16 (dense) and ≤ 1.2e-12 (RS-GDF ROKS CAM-B3LYP); equal fractions − global hybrid ≤ 4.8e-14; zero-XC FD all nine: worst 3.07e-8 / 7.0e-9 at h = 1e-4 / 5e-5 (the ε_zz row, ratio 4.4, h²), Richardson ≤ 8.9e-10 (dense) and ≤ 4.1e-10 (RS-GDF); HSE-type cube Richardson ≤ 3.2e-10 with the attenuated piece 4.5e-3 – 5.0e-3 (resolved ~1e7×); KS FD ≤ 2.7e-8 / 6.2e-9 at h = 1e-4 / 5e-5, Richardson ≤ 8.6e-10; Gamma − 1×1×1 ≤ 3.2e-15; mesh − supercell/2 ≤ 8.3e-141e-10 (anchors); 1e-6 at h = 1e-4 and 2e-8 Richardson (#553's stress bars); 1e-9 (Gamma, supercell). Mutation ledger on the polarized CAM-type cell, UKS and ROKS, dense and RS-GDF: β exchange term dropped 6.5e-2 – 6.6e-2, closed-shell factor ½ per spin 1.5e-1 – 1.6e-1, M-term on the total density 6.0e-2, α K in the β Fock 3.3e-1, f′ slope dropped 1.1e-1 (dense), kernel on J 2.6e-1 (dense), c_sr/c_lr swapped 1.4e-1 – 1.7e-1, plain Madelung strain 3.7e-1, v_M^LR strain dropped 2.5e-1, attenuated fit strain dropped 1.9e-1 (RS-GDF), its scale dropped 2.2e-1 (RS-GDF), W_RO cross term dropped 1.6e-1 (ROKS); smallest 6.0e-2 (≥ 3e6 Richardson bars). On the singlet the β-dropped and ½-factor defects move the stress by 7.5e-2 and the M-term spin defect by exactly 0; the equal-fraction anchor sees the spin defects (7.2e-2, 1.3e-1, 4.8e-2, 3.3e-1). Source mutants of KSpins::exch_terms (½ per spin; β dropped) fail the singlet and FD tests and pass the equal-fraction one (it shares the term list); a plain instead of mixed Madelung shift in the Fock rebuild is refused by the Fock guard in all 8 per-commit tests. Limits: H/STO-3G only (no p or d shells); the singlet anchor and the equal-fraction anchor share the term list / sandwich with their reference side, so FD is the spin-bookkeeping oracle; the HSE06 erf piece is nearly empty on TRI_A, hence the HSE-type cube rows; KS FD on three components (the atom grid's hard neighbour cutoff can step under strain, #495; none seen on these); wB97X accepted, not exercised; the Gamma ROKS RSH stress has no routed entry (kpoint_roks_stress on a 1×1×1 mesh); the Gamma routes on an RS-GDF exchange are the Gamma RS-GDF row below; wired to Python (k-point and Gamma UKS) and the CLI (k-point) by #498, the open-shell RSH bindings rowpbc_kuks_rsh_stress.rs, validation_kuks_rsh_stress.rs, validation_pbc_kuxcstress_ro_rsgdf.rs
Periodic Gamma-point range-separated hybrid nuclear forces (RKS, UKS, ROKS) and stress (RKS, UKS) on RS-GDF J/K (CAM-B3LYP; gamma_rsh::gamma_rks_rsh_gradient / gamma_uks_rsh_gradient / gamma_roks_rsh_gradient / gamma_rks_rsh_stress / gamma_uks_rsh_stress handed a GammaRshExchange::build_rsgdf exchange). A routing change, no new derivative: the Gamma result goes to the 1x1x1 k-point force / stress with the k = 0 KRsGdf::build_rsh fit the SCF's exchange ran on (its config and aux are now kept with it); the routed Fock guard requires the Gamma J to be that fit's JH2 (closed shell) and H3 doublet (open shell) / STO-3G in TRI_A, ET-sp aux, RS-GDF split ω₀ = 1, metric cut 1e-6, SSF (20, 50) atom gridGamma == an INDEPENDENT 1x1x1 k SCF + force / stress (own hcore, J, grid, SCF); central FD of ferric's own Gamma RS-GDF energy (fresh fits + SCF per point, forces and strained cells); Gamma RS-GDF − Gamma dense-AFT RSH force against the PBE0 1x1x1 RS-GDF − dense force gap; a dense Gamma J under an RS-GDF exchange (and the reverse) must be refused. No PySCF oracle (no RS-GDF derivatives, no hybrid stress)Gamma − 1x1x1: force 4.4e-16 (RKS), 1.9e-14 (UKS), 1.1e-14 (ROKS) under ewald, 0 / 1.0e-13 / 1.2e-14 under none; dE/dε 1.8e-15 / 0 (RKS), 1.4e-14 (UKS, H3 doublet, ewald); the routed Fock guard ≤ 5.6e-16 (the two SCFs reach the same density to roundoff on these cells, so this is a GLUE check). FD force (per commit, two components): RKS 2.9e-9 / 5.5e-11 at h = 1e-4; UKS / ROKS (three components) ≤ 3.9e-9 at h = 1e-4 → ≤ 7.5e-10 at 5e-5, Richardson ≤ 3.1e-10. FD stress (ε_zz, ε_xy, ε_yx): RKS (ewald and none) 7.0e-8 / 1.57e-8 at h = 4e-4 / 2e-4 (h²), Richardson ≤ 5.0e-9; UKS (ewald;dE/dε_zz≈ 1.3) 4.0e-7 / 1.0e-7 on ε_zz (ratio 4.0), Richardson ≤ 1.6e-10; below h = 2e-4 the atom grid adds a ~7e-9 non-monotone floor that the dense-AFT Gamma route shows identically. Fit gap: CAM-B3LYP 1.77e-5 / 7.57e-5 / 7.58e-5 vs PBE0 1.71e-5 / 6.92e-5 / 6.93e-5 (ratios 1.03–1.09). Foreign J refused at a Fock mismatch of 4.5e-5–5.1e-5. An RS-GDF Gamma J on another fit than the exchange's (only the 1e-8 Fock guard ties them): metric cut 1e-8 is the same J (every plain-metric eigenvalue ≥ 5.3e-3; ΔE 0, force Δ 0, accepted), ω₀ = 0.9 is the same J to the integral precision (ΔE 6.7e-15, force Δ 2.8e-16, accepted), metric cut 1e-2 (drops 2 of 20; ΔE 1.5e-7, Fock Δ 1.26e-6) and an extra aux s shell (ΔE 4.1e-6, Fock Δ 5.4e-6) are refused; a fit change below the 1e-8 Fock bar would pass with a force error of that order (known limit, not constructed); an aux prepared on other centres is refused by the rebuilt-B check (‖B‖ mismatch 1.95e-4)
Bindings of the open-shell range-separated hybrid derivatives and the Gamma RS-GDF ones (#498; HSE06, CAM-B3LYP): Python run_uks_kpts / run_roks_kpts with_gradient / with_stress and the CLI k-point task = "optimize" / [cell] stress = true for UKS and ROKS on dense-AFT and RS-GDF J/K; the Gamma ROKS force (gamma_rsh::gamma_roks_any_keep + gamma_roks_rsh_gradient; Python run_roks_gamma, CLI Gamma optimize) and the Gamma RKS / UKS force and stress on RS-GDF (Python run_rks_gamma / run_uks_gamma; the CLI Gamma optimize, no CLI Gamma stress by design); the Gamma UKS stress on either J/K (Python). The gate (kxcgrad::check_deriv_functional, dft::check_gamma_{rks,uks,roks}_request) accepts every validated RSH derivative and refuses by name wB97X and the Gamma ROKS stress (no library entry)H3 doublet (UKS, ROKS) and H2 (RKS) / STO-3G, 4 Bohr cube, 1×1×2 Gamma-centred and the Gamma point, omega 0.8, XC 20×50, exxdiv ewald; RS-GDF cc-pVDZ-RI; a p-shell cell (STO-3G + one p shell, ζ = 1, 12 functions for H3, basis size asserted on both sides) for the k ROKS HSE06 force, the k UKS HSE06 stress and the k UKS CAM-B3LYP RS-GDF force; a three-step Gamma ROKS HSE06 optimizationthe library at the same settings (CLI run_end record, bit for bit; Python against those CLI numbers, or for the Gamma UKS and RKS stress the library values printed by the validation file); central FD of the CLI's own energy (k ROKS HSE06 force, k UKS HSE06 σ_zz from a uniaxial strain); Python Gamma against its own 1×1×1 meshCLI = library bit for bit on all 15 CLI anchors (11 routes on H/STO-3G: k ROKS HSE06 / CAM-B3LYP-RS-GDF force, k UKS CAM-B3LYP RS-GDF force, Gamma ROKS CAM-B3LYP / HSE06-RS-GDF force, Gamma RKS HSE06 RS-GDF and UKS CAM-B3LYP RS-GDF force, k UKS HSE06 / CAM-B3LYP-RS-GDF and ROKS HSE06 / CAM-B3LYP-RS-GDF stress; 3 on the p-cell; the moved-geometry Gamma ROKS optimize); FD 6.6e-10 Ha/Bohr (force) and 7.5e-10 Ha/Bohr³ (σ_zz); Python − library on 7 force and 7 stress pins (Gamma RKS HSE06 RS-GDF stress included): energy ≤ 3.8e-15, dense force ≤ 2.3e-15, dense stress ≤ 7.5e-17, Gamma RS-GDF force ≤ 9.1e-15 / stress ≤ 2.4e-15, k RS-GDF force ≤ 3.8e-9 and stress ≤ 2.0e-10 (RS-GDF roundoff on the Python repr-Å vs the CLI 12-decimal xyz, as for the closed-shell RS-GDF row), Gamma − 1×1×1 1.2e-14 (ROKS force) / 1.7e-13 (UKS stress); siblings: HSE06 vs CAM-B3LYP 3.6e-3–5.2e-3 force, 7.6e-4–8.0e-4 stress; RS-GDF vs dense 6.0e-6–3.8e-5 force, 1.1e-7–3.7e-6 stressbit-equal (CLI); 1e-7 (FD force), 1e-8 (FD stress); Python 1e-12 on every dense and Gamma pin, 1e-7 (k RS-GDF force) and 1e-8 (k RS-GDF stress), both below the RS-GDF − dense gaps; 1e-10 (Gamma − 1×1×1). Mutants (all killed): ROKS force handed a state with β = α density (Fock guard 1.27e-1), force × (1 + 1e-6) (FD alone: miss 3.7e-7), transposed or dropped CLI UKS stress, the Gamma ROKS route ignoring its kept exchange, the gate accepting every RSH (wB97X runs), the Python Gamma UKS / RKS stress transposed (2.7e-4 / 1.4e-4). Limits: the fixtures are H only (s and p shells; no d shells or heavy atoms for these routes, #561 covers those for the earlier ones); no Gamma ROKS RSH stressperiodic_open_rsh_derivs.rs, validation_periodic_open_rsh_derivs.rs, test_pbc_rsh_open_mgga_derivs.py
Periodic derivative BINDINGS with p, d and Li shells (CLI and Python vs the library): closed-shell RSH force (k-point HSE06, Gamma CAM-B3LYP), k-point RKS HSE06 stress, k-point UKS SCAN force and SCAN/r2SCAN stress, k-point UKS HSE06 force, Gamma UKS CAM-B3LYP forceH2 / H3 cells with STO-3G + one p shell (ζ = 1), the same + one pure d shell, LiH and the LiH2 doublet / STO-3G; dense AFT, exxdiv ewaldThe library at the same settings (CLI and Python must be bit-equal), and a central FD of the code's own energy along a generic direction (force) or per strain component (stress)CLI = library bit for bit on all 14 rows; FD misses 3.2e-11 to 2.0e-8 on p, d and Li (the H-cell floor); Python binding − CLI ≤ 1.6e-15 on the seven p-shell rows it compares (five force, two stress); the d-shell and Li Python tests are FD-only. Mutation: dropping the p lowering term of the x-derivative (pair_ft/residues.rs) misses FD by 8.5e-6 to 2.6e-4 (stress 6.3e-6 to 1.1e-4), which the s-only H anchors and the Gamma-vs-1×1×1 check cannot seeBit-equal; FD 5e-8 (Rust per commit, Gamma RKS CAM-B3LYP force), Python force 1e-8, stress 1e-7. Limits: the per-commit gate is 2 bit-equal anchors and 1 FD anchor; the rest is the validation tier (~70 min CI) and the nightly Python job. No d shell on a heavy atom (dense-AFT cost)periodic_heavy_anchors.rs, validation_periodic_heavy_anchors.rs, test_pbc_heavy_anchors.py
Periodic open-shell k-point UKS with the pure meta-GGAs SCAN and r2SCAN (energy only; dense-AFT J/K; library solve_kuks, Python run_uks_kpts and the CLI UKS route)H3 doublet (cubic, 4 Bohr) and H4 triplet (triclinic) / STO-3G, meshes 1×1×1 and 1×1×2 (singlet anchors also 1×1×3, 2×2×2)PySCF 2.13.1 pbc.dft.KUKS + AFTDF, UniformGrids 61³, cell.precision 1e-12 (8 polarized pins); singlet k-UKS against k-RKS and gamma_rks; polarized k-mesh against the explicit supercell; V_σ against a finite difference of E_xc per spin≤ 7.4e-13 Ha vs PySCF; singlet k-UKS = k-RKS exactly (0 on every case), 1×1×1 = gamma_rks to 2.7e-15; k-mesh = supercell / N to 8e-14; V_σ vs FD ≤ 1.2e-8 relative1e-9; 1e-10; 1e-8; 2e-7 relative. Tau mutants (swapped τ_α/τ_β, dropped β V^τ, total τ in both channels, spin-summed evaluator) miss the pins by ≥ 2.0e-5 Ha (the energy is only second order in a dropped β V^τ); the FD check fails under swapped and dropped-β V^τ by ≥ 5.5e-4 relative. CLI: the UKS energy (H3 doublet, Gamma-centred and Monkhorst-Pack 1×1×2) equals solve_kuks to 1e-12 (measured 0). Limits: forces and stress are the "k-point UKS analytic forces and stress" row further down, k-ROKS in its own row below, RS-GDF in its own row below, no meta-GGA hybrids or TPSS; the Gamma-point UKS / ROKS meta-GGA energy is the next row (library only); no level shift was needed, but only on these 3 to 4 orbital minimal-basis cells; the singlet anchor shares chunk_tau between both sidesvalidation_pbc_kuks_mgga.rs, pbc_kuks_mgga.rs, gen_pyscf_kuks_mgga_refs.py, periodic_kpoint_open_shell.rs, validation_periodic_kpoint_open_shell.rs, validation_pbc_kuks.rs
Periodic Gamma-point open-shell meta-GGA energy, UKS and ROKS, SCAN and r2SCAN (library dft::gamma_uks / rohf::gamma_roks, dense-AFT J; per-spin τ_σ from D_σ via ferric-dft's eval_tau_uks into the unchanged polarized kernel, wiring only). Wired to Python run_uks_gamma / run_roks_gamma and the CLI Gamma UKS / ROKS routes by #498 (dense-AFT J/K only; the RS-GDF J/K refused by name before any integral; CLI energy equals the library bit for bit, crates/ferric-cli/tests/periodic_open_mgga_energy.rs, UKS SCAN and ROKS r2SCAN with a p shell on every H); its force and stress refuse it by nameH3 doublet (cubic, 4 Bohr) / STO-3G (⟨S²⟩ 0.7527); singlets H2 (cubic, 4 Bohr) and H4 (triclinic)the k-point UKS on a 1×1×1 mesh (complex Bloch AOs, independent τ construction, same atom grid); Gamma RKS for the singlets; PySCF 2.13.1 Gamma pbc.dft.UKS / pbc.dft.ROKS + AFTDF, UniformGrids 61³, cell.precision 1e-12 (4 pins; the PySCF Gamma UKS SCAN reproduces the existing KUKS 1×1×1 pin to 1.1e-13); V_σ against a central FD of E_xc along each spin's HOMO→LUMO rotationGamma UKS = 1×1×1 k-UKS to 2.0e-15 Ha (E_xc 6.2e-12); singlet UKS / ROKS = RKS to 1.8e-15; ≤ 2.9e-13 Ha vs PySCF (UKS and ROKS, SCAN and r2SCAN); V_σ vs FD ≤ 6.1e-9 relative (h = 2e-5, scales as h²)1e-10; 1e-10; 1e-9; 1e-7 relative. Tau mutants (τ_β from D_α, τ omitted, swapped τ, dropped β V^τ) miss the 1×1×1 anchor by ≥ 2.0e-5 Ha and the PySCF UKS / ROKS pins by ≥ 1.2e-5 Ha (dropped β V^τ is second order in the energy); the FD check fails under swapped τ and dropped β V^τ by ≥ 0.26 relative; the singlet anchors are blind to all of them by construction. Limits: energy only (no Gamma open-shell meta-GGA force or stress), dense-AFT J only (RS-GDF J/K refused by name), ROKS with the DIIS solver only (the second-order solver, whose XC Hessian is a finite difference along an arbitrary ΔD, refused by name), SCAN / r2SCAN only (TPSS refused), one 3-orbital minimal-basis cell. The β channel holds ONE orbital, so τ_β = τ_W,β exactly and an arbitrary ΔD_β meets SCAN's clamped iso-orbital kink (measured 7.95e-2 relative FD miss); the FD therefore uses an idempotent rotationpbc_gamma_open_mgga.rs, validation_pbc_gamma_open_mgga.rs, gen_pyscf_gamma_open_mgga_refs.py, validation_pbc_rohf.rs
Periodic k-point RKS (PBE, PBE0) on an all-electron cell with a 1s core (LiH, no ECP) and on a 2×2×1 meshLiH (triclinic-ish cell, a = 5, 5, 6 Bohr with a 0.4/0.3 tilt) / PySCF STO-3G, Gamma PBE; H2 (cubic, 4 Bohr) / STO-3G, 2×2×1 Gamma-centred and shifted, PBE and PBE0, plus an orthorhombic 4.0 × 4.4 × 4.0 cell (PBE)PySCF pbc.dft.KRKS + AFTDF, cell.precision 1e-10; LiH on a 35³ XC grid with an 80³ J mesh, H2 on 31³LiH Gamma PBE: ferric − PySCF = −6.5e-9 Ha; H2 2×2×1: ≤ 2.1e-12 Ha (4 cubic rows; orthorhombic rows see the run)LiH bar 1e-7, 2×2×1 bar 1e-7. Sensitivities (ferric against ferric; no PySCF pin behind the LiH ones): dropping exxdiv moves LiH PBE0 by 2.6e-1 Ha and H2 2×2×1 PBE0 by 9.2e-2 Ha; dropping the mesh shift moves LiH PBE by 3.3e-3 Ha (thresholds 1e-3). Limits: PySCF's default AFT J mesh (35³) is 4.8e-3 Ha off for the Li 1s density (55³ 2.2e-5, 80³ 6.5e-9 from ferric), so the LiH pin requires the 80³ J mesh and only one LiH row exists (no shifted or PBE0 pin); no cell heavier than Li; the cubic H2 cell is a↔b symmetric, so an a↔b swap is seen only by the orthorhombic 4.0 × 4.4 × 4.0 PBE rows; the open-shell UKS pins in pbc_kuks.rs stay at 61³ (issue #356 item not met: ferric at 31³ misses the 61³ PBE triplet pin by 3.8e-8 Ha, over its 1e-8 bar; the 3.8e-8 at 31³ does not fit the earlier record that 40³ was 1.2e-5 off (non-monotone in mesh size; not reconciled: the 40³ run is not on record and the two may differ in system and functional); a same-grid PySCF 31³ LDA re-pin converged to another SCF state)validation_pbc_kks_core.rs, pbc_krks_2x2x1.rs, gen_pyscf_kks_core_refs.py, gen_pyscf_kks_h2_31_refs.py
Periodic open-shell k-point UKS analytic forces and stress with the pure meta-GGAs SCAN and r2SCAN (library kpoint_uks_gradient / kpoint_uks_stress, dense-AFT J/K only; wired to Python run_uks_kpts and the CLI by #498: CLI force and stress equal the library bit for bit (SCAN / r2SCAN optimize, H3 1×1×2; stress SCAN Gamma-centred, r2SCAN Monkhorst-Pack), Python vs library force ≤ 5.6e-17, Python force vs FD of its own energies 7.6e-10 / 3.8e-10): the per-spin tau terms (v_τσ with D_σ in the AO-derivative and strain moments, the closed-shell tau kernels reused per spin; V^τ_σ in the Pulay Fock and the Fock guard)H3 doublet (cubic, 4 Bohr; 1×1×3 non-TRIM and 1×1×2) and H4 triplet (triclinic, 1×1×2) / STO-3G; H2 singlet 1×1×3; atom grid 20×50 (neighbour cutoff 14 Bohr) and uniform 61³singlet k-UKS vs k-RKS per part; fixed-density XC part vs central FD of the polarized E_xc (no SCF) on every component with an h sweep; FD of ferric's own converged energy (every force component, all nine strain components, h sweep); 1×1×3 vs the explicit 3-cell supercell (multiplicity 4); PySCF 2.13.1 pbc.grad.kuks_stress (FFTDF + UniformGrids 61³, its k-point UKS force refuses meta-GGA, so the force has no external pin)singlet = RKS to 8.9e-16; PBE through the tau path = PBE exactly; fixed-D XC force 6.5e-10 (h 1e-4) → 7.8e-11 (h 2.5e-5, falling to the roundoff floor of the fixed-density energy, not h²-limited), XC stress 5.5e-8 → 3.5e-9 (the h² stencil, 4x per halving); force vs SCF FD (9 components, H3) 8.4e-9 / 2.0e-9 / 7.7e-10 at h = 2e-4 / 1e-4 / 5e-5, H4 triplet (12) 8.9e-9; dE/dε vs SCF FD (9, H3) 6.4e-8 / 1.5e-8 / 5.3e-9 at h = 1e-4 / 5e-5 / 2.5e-5, H4 triplet 3.5e-8; supercell force per part ≤ 1.1e-13, σ ≤ 1.8e-14; σ vs PySCF 5.8e-11 / 7.7e-11 Ha/Bohr³ (SCAN / r2SCAN; 3.7e-9 / 4.9e-9 Ha per 64 Bohr³ cell), of which the fixed-density XC part accounts for essentially all (XC part vs PySCF get_vxc 3.7e-9 / 5.1e-9 Ha); PySCF's own analytic σ vs central FD of its own strained energy (h = 1e-4, --fd) 1.3e-9 / 1.4e-9 Ha/Bohr³, run-to-run PySCF reproducibility ≤ 7e-131e-11; exact; 1e-7; 1e-7 (stress asserted at the smallest h, plus a > 2.5x fall per halving from h = 1e-4); 1e-9 / 1e-10; 1e-9 Ha/Bohr³ (σ), 1e-7 Ha (XC part). Spin/tau mutants on the polarized cell (swapped τ_α/τ_β, dropped β tau term, total τ in both libxc channels, dropped β V^τ, all spin kernels swapped, spin-averaged form, tau term dropped, doubled or with transposed strain indices) miss the FD anchors by 6.8e-4 to 9.9e-2 and PySCF by ≥ 1.9e-4; every spin mutant is invisible in the singlet anchor. Limits: H-only minimal-basis cells, no p/d shells, no ECP or heavy atom, SCAN's α ≈ 1 region not reached, RS-GDF J/K in the next row, the ROKS meta-GGA energy, force and stress are their own row below, the (75, 302) production grid not FD-checkedvalidation_pbc_kuxc_mgga.rs, pbc_kuxc_mgga.rs, gen_pyscf_kuks_mgga_stress_refs.py, validation_periodic_rsh_open_mgga_derivs.rs, validation_pbc_kuxcstress.rs
Periodic open-shell k-point UKS SCAN / r2SCAN on the RS-GDF J/K: energy, analytic force and stress (library solve_kuks with jk = rsgdf, kpoint_uks_gradient / kpoint_uks_stress with KGradJk::RsGdf / KStressJk::RsGdf; issue #492; the energy is wired to Python run_uks_kpts(jk="rsgdf") and the CLI UKS route by #498, CLI equal to the library bit for bit, periodic_open_mgga_energy.rs; the force and stress are not wired, the bindings' derivative gate refuses them on RS-GDF)H3 doublet in the cubic 4 Bohr cell: one s per H (alpha 0.5) with the exact pair-midpoint aux (energy), STO-3G with an even-tempered s+p aux (derivatives); H2 singlet; 1×1×1 and 1×1×3; uniform 32³ / 24³ and atom 20×50 (cutoff 14 Bohr) gridsexactness anchor: exact-aux RS-GDF energy vs dense-AFT (incomplete-aux negative control); PySCF 2.13.1 pbc.dft.KUKS FFTDF + UniformGrids 32³ at that exact-aux limit; singlet UKS vs RKS on RS-GDF (energy, force, stress); FD of the converged RS-GDF SCF energy on all 9 force and all 9 strain components (weekly; one polarized strain component plus the translation sum per commit)anchor ≤ 2.1e-13 Ha; vs PySCF ≤ 1.5e-13 Ha (4 pins); singlet ≤ 2.6e-15; force vs FD 2.0e-9 / 1.9e-9 (SCAN / r2SCAN, h = 1e-4); dE/dε vs FD 7.8e-8 / 5.8e-8 (h = 1e-4, the h² stencil)1e-10; 1e-9; 1e-10; 1e-7 (force), 5e-7 (stress). Spin/tau mutants and a one-spin Coulomb in the (route-independent) Pulay Fock miss the pin by ≥ 3.0e-5 Ha and the FD by ≥ 2.1e-3; the singlet anchor catches every asymmetric derivative defect (≥ 2.4e-3) but not the α/β swap (1.4e-15); a source mutant (τ_β from D_α) misses the pin by 4.1e-2 while the anchor and the singlet stay green. Limits: no PySCF force/stress pin (PySCF has no k-UKS meta-GGA force, and the k-point RS-GDF derivative needs atom-centred aux, so the exact-aux construction is energy-only); H-only minimal-basis cells; the closed-shell "RS-GDF − dense transfers from PBE" check holds only to ~12% on the polarized cell and is not usedvalidation_pbc_kuks_mgga_rsgdf.rs, pbc_kuks_mgga_rsgdf.rs, gen_pyscf_kuks_mgga_rsgdf_refs.py
Periodic restricted open-shell k-point ROKS with the pure meta-GGAs SCAN and r2SCAN: energy, analytic force and stress (dense-AFT J/K, the RS-GDF J/K refused by name in the SCF; library solve_kroks, kpoint_roks_gradient, kpoint_roks_stress, issue #492; the energy is wired to Python run_roks_kpts and the CLI ROKS route by #498, CLI equal to the library bit for bit for SCAN with a p shell on every H and r2SCAN / STO-3G, crates/ferric-cli/tests/periodic_open_mgga_energy.rs; the force and stress are not wired, the bindings' derivative gate refuses them by name). The k-ROKS SCF runs on the k-UKS per-spin-tau evaluator KPeriodicXcPolarized (its own tau-less duplicate deleted); the force and stress are the k-UKS meta-GGA assembly on the ROHF spin densities with W_RO = sym(D_α F_α D_α + D_α F_β D_β) from the rebuilt spin Focks (which carry V^τ_σ). No new formula: wiring, plus lifting the derivative refusalH3 doublet (N_α = 2, N_β = 1) in the cubic 4 Bohr cell, one s per H (α 0.5), uniform 32³; H2 singlet (one s per H) and H2 triplet (two s per H, exponents 0.8 / 0.3, N_β = 0); meshes 1×1×1 and 1×1×3 (complex Bloch AOs)Energy: singlet k-ROKS against k-RKS (unpolarized kernel, total τ); polarized H3 doublet (STO-3G, atom grid 40×110) 1×1×1 k-ROKS against Gamma ROKS (eval_tau_uks on real AOs vs chunk_tau on Bloch AOs, pbc_gamma_open_mgga.rs); fully polarized (N_β = 0) k-ROKS against k-UKS (the ROHF and UHF spaces coincide); PySCF 2.13.1 pbc.dft.KROKS (its veff is its KUKS one on the ROHF occupations) + FFTDF 40³, UniformGrids 32³, nelec = (2 N_k, N_k). Force and stress (no external pin: PySCF has no k-point ROKS gradient and its KUKS force refuses meta-GGA): singlet k-ROKS vs k-RKS and fully polarized (N_β = 0) k-ROKS vs k-UKS per part (independent SCFs); every force and strain component on the H3 doublet 1×1×3 against the central FD of ferric's own converged k-ROKS energy (force h = 1e-4 / 5e-5; stress Richardson-extrapolated from h = 5e-5 and 2.5e-5, the diagonal strains carrying an h² stencil error of ~3e-8 at 5e-5, its ≥ 2.5× fall per halving asserted); the 1×1×1 k-ROKS SCAN force and stress against the central FD of the Gamma-point ROKS energy (real AOs, eval_tau_uks, its own SCF; the two energies are asserted equal to 1e-10 at the base geometry and at the 18 strained geometries of h = 5e-5; measured 2.2e-16 and ≤ 1.6e-14)singlet ≤ 2.2e-16 (E_xc ≤ 1.1e-16); 1×1×1 vs Gamma ROKS 3.6e-15 / 5.6e-15 (SCAN / r2SCAN; E_xc ≤ 8.2e-15), Gamma-side τ mutants miss it by ≥ 1.2e-5 and k-side τ_β-from-D_α / swapped V^xc by 4.3e-2 / 2.3e-4; fully polarized ≤ 8.8e-15 (E_xc ≤ 2.6e-13); vs PySCF ≤ 5.4e-14 Ha on 4 pins, ⟨S²⟩ = 3.75 at 1×1×3; ROKS lies 1.4e-4 to 1.2e-3 Ha above PySCF KUKS on the same setup. Force / stress: singlet per part ≤ 4.4e-16 / 7.8e-16 (SCAN / r2SCAN), fully polarized ≤ 1.9e-12 / 5.5e-14; vs FD of the own energy (9 + 9 components, orbital gradient ≤ 1.5e-11) force 2.3e-9 → 8.5e-10 (SCAN, h = 1e-4 → 5e-5), 7.4e-10 (r2SCAN), dE/dε 7.3e-10 / 8.4e-10 (Richardson; raw 3.2e-8 / 2.7e-8 at h = 5e-5); vs FD of the Gamma ROKS SCAN energy force 7.4e-10, dE/dε 6.4e-10; per commit, one diagonal strain (1,1) raw at h = 5e-5: 3.2e-8 (bar 1e-7; the grid-weight volume term dropped misses it by 1.2)1e-10; 1e-9; 1e-9 (energy); 1e-11 (anchors), 1e-8 (FD). Derivative mutants (test hooks, against the polarized FD, SCAN / r2SCAN): v_τα ↔ v_τβ swapped 5.1e-3 / 4.9e-3 (force), 3.4e-2 / 3.5e-2 (stress); β tau term dropped 6.8e-3 / 7.3e-3, 3.3e-2 / 3.5e-2; total τ in both libxc channels 6.0e-3 / 5.3e-3, 3.3e-2 / 3.4e-2; W_RO cross term dropped 5.6e-2, 3.5e-1; tau term dropped or doubled 1.4e-2 / 1.5e-2, 7.1e-2 / 7.4e-2; V^τ dropped from the Pulay Fock with the guard and the ROHF gate off 2.0e-2 / 2.2e-2, 3.4e-2 / 3.7e-2; β V^τ dropped (guard off, gate on) is REFUSED by the ROHF orbital-gradient gate (g = 4.5e-3 / 4.6e-3, evaluated on the rebuilt Focks); the unrestricted-form W is an identity at convergence (≤ 8.5e-10); transposed tau strain indices are invisible on the symmetric uniform-grid stress (≤ 8.4e-10; caught on the atom grid of the UKS row). Reach: the singlet anchor is blind to the τ swap only, the fully polarized one to the β tau term, β V^τ and the cross term. Energy mutants (test hooks): τ_β from D_α misses the pins by ≥ 3.3e-2 (singlet blind, 2.2e-16; fully polarized: SCF does not converge); τ dropped from the kernel input ≥ 3.1e-2 and fails both anchors (≥ 1.5e-2); V^xc_α / V^xc_β swapped between the Roothaan spin Focks ≥ 1.6e-4 (singlet blind; fully polarized anchor 1.8e-2); bare α Fock diagonalised ≥ 9.7e-4 (both anchors blind). Source mutants (edited, run, reverted): τ_β from D_α in the shared evaluator misses the SCAN 1×1×3 pin by 3.3e-2 (singlet green); a Roothaan coupling term P_o†F_βP_c → P_o†F_αP_c misses it by 1.3e-3 (both anchors green); widening the ROKS gate to TPSS fails kroks_refuses_unvalidated_meta_gga. Limits: the derivatives are FD-checked on the uniform 32³ grid only (no grid response; the atom-grid response is the k-UKS one, validated in the UKS row); no external derivative pin; the Gamma-point ROKS meta-GGA force and stress still refuse; RS-GDF J/K refused by name in the SCF (no pin; removing the refusal lets the library run it, mutation-tested) and, defensively, in the ROKS meta-GGA force and stress (kroks_meta_gga_derivatives_refuse_rsgdf, mutation-tested); TPSS refused; H-only minimal-basis cellsvalidation_pbc_kroks_mgga.rs, pbc_kroks_mgga.rs, gen_pyscf_kroks_mgga_refs.py, pbc_gamma_open_mgga.rs
Periodic closed-shell meta-GGA energy, TPSS (k-point RKS and Gamma-point RKS; its forces and stress are the next row), and SCAN / r2SCAN / TPSS on the RS-GDF J/KH4 / STO-3G triclinic cell (two occupied orbitals, so SCAN, r2SCAN and TPSS all differ), meshes 1×1×1 and 1×1×2 and the single Gamma point; RS-GDF: H2 / STO-3G (4 Bohr cube) at 1×1×1 and 1×1×3, aux cc-pVDZ-RI and def2-universal-jkfit, and H2 with one s per H plus the 24 pair-midpoint aux s functions (the exact-fit anchor)PySCF 2.13.1 pbc.dft.KRKS and pbc.dft.RKS (TPSS = MGGA_X_TPSS + MGGA_C_TPSS) + AFTDF, UniformGrids 61³, exxdiv ewald, cell.precision 1e-13; RS-GDF: ferric's dense-AFT J/K on the same grid, with the fixed-density Coulomb fit error E_J^fit(D) − E_J^dense(D) at the dense SCF density as the oracleTPSS ≤ 2.5e-13 Ha vs PySCF (Gamma 2.4e-13, 1×1×1 2.5e-13, 1×1×2 1.6e-14); 1×1×1 = gamma_rks to 4.9e-15; V_xc vs FD of E_xc 2.5e-7 (1.1e-7 relative). RS-GDF: exact-fit anchor 3.4e-14 Ha; gap minus fixed-density fit error ≤ 1.7e-15 at 1×1×1, −8.8e-8 (1.1e-3 of the fit) at 1×1×3 cc-pVDZ-RI, −3.5e-10 with def2-universal-jkfit1e-9; 1e-8; 1e-6 relative; RS-GDF anchor 1e-10, relaxation term non-positive and < 1e-2 of the fit. TPSS is 1.4e-2 Ha from SCAN on this cell (asserted). The incomplete-aux anchor control misses by ≥ 2.3e-4 Ha. Mutants: TPSS mapped to the SCAN pair misses the pins by 1.4e-2 Ha; V^tau zeroed misses by 2.4e-5 Ha (1×1×1); V^tau zeroed on the RS-GDF route only moves the exact-fit anchor by 6.5e-5 Ha (the symmetric 1×1×1 H2 fit cases are blind to it). Limits: open-shell TPSS not validated; no meta-GGA hybrids; minimal-basis cells only. The PBE (plain-functional) RS-GDF gap matches the meta-GGA gap exactly only at 1×1×1, where the density is fixed by symmetry; at 1×1×3 it is 4 to 10 % off (the fit error of a different density)pbc_krks.rs, pbc_krks_mgga_rsgdf.rs, gen_pyscf_pbc_tpss_refs.py, validation_pbc_krks.rs
Periodic closed-shell meta-GGA forces and stress: TPSS on a k-point mesh (kpoint_rks_gradient / kpoint_rks_stress, dense-AFT J; Python run_rks_kpts with_gradient / with_stress, CLI k-point optimize and [cell] stress), and SCAN / r2SCAN / TPSS at the Gamma point routed through the 1×1×1 mesh (library gamma_mgga::gamma_rks_mgga_gradient / gamma_rks_mgga_stress, dense-AFT J; Python run_rks_gamma with_gradient / with_stress, CLI Gamma optimize)H2 / STO-3G in the 4 Bohr cube ((20, 50) atom grid, 1×1×3 k-mesh and the Gamma point; uniform 24³ and 61³ grids at 1×1×2 / 1×1×3 for the stress); weekly: the two-orbital H4 / STO-3G triclinic cell at the Gamma point (SCAN, r2SCAN, TPSS) and at 1×1×2 (TPSS, 1.4e-2 Ha from SCAN), and the 3-cell supercellPySCF 2.13.1 pbc.grad.krks_stress with xc = TPSS (FFTDF, UniformGrids 61³, 1×1×2, exxdiv ewald; PySCF's own analytic-vs-FD agreement 2.3e-10; PySCF's KRKS nuclear gradient raises NotImplementedError for every meta-GGA, so there is no PySCF force pin); central FD of ferric's own SCF energy (k-point TPSS; the Gamma SCF energy for the routed Gamma derivatives, an independent construction); the native Gamma force / stress for PBE through the same route; Gamma = 1×1×1; k-mesh = supercellTPSS stress vs PySCF 7.5e-11 Ha/Bohr³; k-point TPSS force vs FD 2.7e-9 Ha/Bohr, dE/dε vs FD 1.1e-8; Gamma routed force vs FD of the Gamma energy 2.3e-9 (SCAN, r2SCAN, TPSS alike: the h² truncation of the bond-direction third derivative, 9.2e-9 at h = 2e-4), dE/dε 3.0e-8; routed PBE vs native Gamma force 1.3e-15, dE/dε 2.1e-14; Gamma vs 1×1×1 force ≤ 1.4e-16 (Fock guard ≤ 8.9e-16); weekly: H4 triclinic Gamma force vs FD 1.6e-10 / 5.1e-10 / 1.1e-9 (SCAN / r2SCAN / TPSS), k-point TPSS 3.0e-9, dE/dε 1.6e-8; TPSS 1×1×3 vs supercell force 1.2e-14, dE/dε 6.9e-13PySCF 1e-9; force FD 1e-7; k stress FD 5e-7; Gamma stress FD 3e-7; glue 1e-9. Mutants (miss vs the FD or pin): TPSS mapped to the SCAN pair in the libxc name table 7.8e-5 vs the PySCF stress pin (FD of ferric's own energy is BLIND to it, 2.5e-9; the per-commit force tests also fail it through the Fock guard and a SCAN-vs-TPSS gap of 1.6e-3 / 8.2e-3); tau AO term dropped / doubled ≥ 1.7e-2; tau left out of the point motion 3.5e-3 (k) / 4.1e-3 (Gamma); V^tau zeroed in the Pulay Fock 5.8e-3 (k) / 1.4e-3 (Gamma) force, 2.3e-2 / 3.0e-2 stress (with the guard on it is refused); grid weight response dropped ≥ 1.1e-1; tau strain dropped / doubled ≥ 3.0e-2; StressTauPath is an equivalent mutant on a meta-GGA (0, asserted). Refused (typed, asserted): an RS-GDF Gamma SCF (Fock guard, 9.5e-6 > 1e-8; the bindings refuse jk = rsgdf before the SCF), another grid or functional in the config, an open-shell result, range-separated functionals or kernels. Limits: closed shell only (open-shell and Gamma UKS/ROKS meta-GGA refused); on the one-orbital H2 cell SCAN and r2SCAN coincide exactly (α = 0 everywhere, 5.6e-17, asserted), so only the H4 rows separate them; TPSS and the Gamma route on the dense-AFT J only (the bindings refuse a TPSS force / stress and a Gamma meta-GGA force / stress on jk = "rsgdf" by name, before the SCF); minimal-basis H cells; the 1×1×1 kernels cost 32 nao⁴ bytes on top of the Gamma ERIpbc_gamma_mgga_derivs.rs, validation_pbc_mgga_derivs.rs, pbc_kxcgrad_mgga.rs, pbc_kxcstress_mgga.rs, gen_pyscf_kks_stress_refs.py, periodic_kpoint_ks_optimize.rs, periodic_kpoint_stress.rs, validation_periodic_kpoint_ks_optimize.rs, validation_pbc_kxcstress_mgga.rs
Periodic XC grid neighbour switch (PeriodicGridConfig::neighbour_switch W, CLI [cell] neighbour_switch; default 0 = the hard image cutoff, bit-identical): a quintic taper of each image atom's presence over [D − W, D] in the SSF/Becke partition, with its derivative in the Gamma and k-point KS force and stressH2/STO-3G, cubic 4 Bohr, PBE, atom grid (20, 50), D = 10 Bohr, W = 2 Bohr, dense AFT, Gamma and 1×1×3; kernel: a mixed Li/H/C cluster (SSF and Becke); grid level: Li–H cell, (12, 26), D = 6.5, W = 1.5 (SSF and Becke), plus a sparse 9 Bohr Li–H cell at D = bound + W (Becke)central FD of ferric's own SCF energy at h = 1e-4 and 5e-5 (6 force, 9 strain components), hard vs switched; dense scans (61 points, step 1e-4) of ε_11 and an H x-coordinate, fourth differences; kernel vs FD and vs the unswitched kernel; grid functional Σ W F vs fourth-order FD under displacement and strainswitched: force vs FD 2.7e-9 (k) / 2.3e-9 (Gamma), stress 2.5e-8 / 2.9e-8 at h = 1e-4, 5.9e-9 / 7.1e-9 at 5e-5 (h²); hard on the same cells: stress 2.0e-6 / 2.9e-6 at h = 1e-4 (a straddled jump). Scans: hard maxΔ⁴E6.7e-7 (strain) / 4.8e-8 (displacement), largest single-step jump ~2.2e-7 / 1.6e-8 Ha; switched 2.4e-13 / 7.6e-14. Energy vs hard D = 20 (Gamma): hard D = 10 +3.9e-6, D = 12 −4.8e-8; switched (D, W = 2) = (10) +1.1e-5, (12) +1.7e-7, (14) −3.8e-9, each between the hard energies at D − W and D (asserted). Grid build (Li–H, (75, 302), 3 threads): hard D = 10 0.29 s, switched D = 10 / W = 2 0.46 s, automatic D = 12 / W = 2 0.87 s. Kernel: all-present limit = unswitched to 1e-14; derivative vs FD 2.6e-10; grid functional force / stress vs FD 2.3e-10
Periodic XC grid neighbour switch on the open-shell, meta-GGA and RSH derivative paths (#495), and the Python neighbour_switch keywordGamma UKS / ROKS PBE (native grad.rs / stress.rs), k UKS / ROKS PBE 1×1×2 (kuxcgrad / kuxcstress), Gamma RKS SCAN (1×1×1 route), k UKS SCAN 1×1×1, Gamma RKS CAM-B3LYP force + stress, Gamma UKS / ROKS CAM-B3LYP force (1×1×1 route); H2 / H3 doublet STO-3G, cubic 4 Bohr, (20, 50), D = 10, W = 2 Bohr, exxdiv none, dense AFT. Python: the six KS bindings on H2 / H3W = 1e-9 against the hard cutoff (exactness anchor, against the unswitched kernel); central FD of ferric's own switched SCF energy (all force and nine strain components) at h = 1e-4, 5e-5 and their Richardson combination. Python: automatic cutoff with W = explicit 10 Bohr + W (the library adds W, so the Å → Bohr conversion is visible), the Gamma energy shift E(W) − E(hard) against the library's, Gamma = 1×1×1 mesh with the switch, refusalsanchor ≤ 7.9e-15 on every path; force vs FD ≤ 4.8e-9 (h = 1e-4), Richardson ≤ 9.9e-10 (the force sits at the ~1e-9 SCF noise floor, not h²-limited); stress vs FD ≤ 3.3e-7 at h = 1e-4 (k UKS SCAN), falling ≈3.7–4.4× per halving on every row, Richardson ≤ 2.5e-9. Python: cutoff identity < 1e-12, shift +7.237e-6 vs library +7.236e-6, Gamma − 1×1×1 < 1e-10anchor 1e-12; force 1e-7 (h = 1e-4); stress 1e-6 (h = 1e-4; the H3 cells' h² truncation is 3× #504's); Richardson 2e-8; Python shift 2e-8. Mutants: dropped image presence derivative misses on all nine paths by 1.7e-6–4.0e-6 (force) and 2.7e-5–5.5e-5 (stress), the same at every h; 1 − f s cell factor fails every anchor (≥ 0.37 Ha); binding unit slip and dropped W fail 7 and 10 of the 36 Python tests. Limits: H-only cells, minimal basis, one W, (20, 50) only; the open-shell RSH stress was not a library path when this row was measured (it landed later, library only, with its own row; the binding gates still refuse it by name); ROKS meta-GGA has no SCF; Python is compared with the library through the shift and the cutoff identity, not bit for bit (cdylib, no Rust harness)validation_pbc_grid_switch_paths.rs, test_pbc_neighbour_switch.py
Periodic restricted open-shell range-separated hybrids (ROKS: HSE06, CAM-B3LYP; energy only) at the Gamma point (gamma_roks_rsh) and on a k-mesh (solve_kroks), dense AFT and RS-GDFThe UKS row's cells: H3 doublet and H4 triplet / STO-3G, triclinic, Gamma and 1×1×2 (and 1×1×1), exxdiv ewald and nonePySCF 2.13.1 pbc.dft.ROKS / KROKS + AFTDF (mf.nelec = (N_a N_k, N_b N_k); every row converged in PySCF), 11 energies read from the committed JSON; closed-shell limit against RKS (dense: H2 and an H4 singlet with SCF freedom; RS-GDF: only H2, cubic a = 4 Bohr, HSE06, 1×1×1, where HSE06's erf part is empty, pbc_krsh_rsgdf.rs); 1×1×1 = Gamma; 1×1×2 mesh = 6-atom supercell / 2; E(ROKS) ≥ E(UKS) on the 9 rows with both PySCF pins (sanity, not a bar)≤ 4.4e-13 Ha vs PySCF; ROKS − RKS ≤ 4.0e-15; 1×1×1 − Gamma 0.0; mesh − supercell/2 ≤ 1.7e-14; ROKS − UKS +5.1e-6 to +3.1e-3; RS-GDF − dense 0.92–1.84 × the PBE0 UKS plain-Coulomb fit error1e-11; 1e-11; 1e-11; 1e-10; ROKS − UKS sign only; RS-GDF < 5 × the fit error. Mutants on the polarized CAM-B3LYP H3 cell moved E by ≥ 7.4e-4: α K on the β Fock 1.2e-2, β K dropped 1.5e-1, halved Madelung 2.2e-1, plain instead of mixed Madelung 5.0e-1, attenuated K scaled by c_sr 3.9e-1, bare α Fock instead of Roothaan 7.4e-4 (α-on-β and bare-α are 0.0 in the closed-shell limit); source mutants (plain Madelung at Gamma, unattenuated K at Gamma, k-point dense J/K built without the attenuation) moved the pins by 0.21–0.75. Limits: as the UKS row (the G ≠ 0 erf kernel is live only for CAM-B3LYP on this cell; the kernel is shared with RKS/UKS, so only PySCF is independent of it; H/STO-3G only; wB97X is accepted but no test runs ROKS with it); bindings (#498): Python run_roks_kpts / run_roks_gamma and the CLI ROKS route (gamma_rsh::gamma_roks_any, solve_kroks); the CLI energy equals the library bit for bit (k-mesh HSE06 and Gamma CAM-B3LYP, H3 doublet, cubic 4 Bohr), Python vs library 4.8e-14 / 7.1e-14 (bar 1e-12), PBE0 / B3LYP siblings 2.5e-4 / 2.5e-2 away; no range_split (the ROKS force and the open-shell stress have their own rows, wired by #498)pbc_roks_rsh.rs, validation_roks_rsh.rs, gen_pyscf_roks_rsh_refs.py
Periodic open-shell range-separated hybrids (UKS: HSE06, CAM-B3LYP; energy only) at the Gamma point and on a k-mesh, dense AFT and RS-GDFH3 doublet and H4 triplet / STO-3G in a triclinic cell (polarized, non-degenerate: ⟨S²⟩ 0.753 and 2.000), Gamma and 1×1×2 (and 1×1×1), exxdiv ewald and nonePySCF 2.13.1 pbc.dft.UKS / KUKS + AFTDF (mf.nelec = (N_a N_k, N_b N_k)), 16 energies read from the committed JSON; closed-shell limit against RKS (H2 and an H4 singlet with SCF freedom); 1×1×1 = Gamma; 1×1×2 mesh = 6-atom supercell / 2≤ 4.4e-13 Ha vs PySCF (PBE0 on the same cell 3.7e-13); UKS − RKS ≤ 4e-15 (H4 singlet) and 0.0 (H2, symmetry-fixed: tests the energy assembly only); 1×1×1 − Gamma 3.3e-15; mesh − supercell/2 ≤ 4.7e-15; RS-GDF − dense equals the plain-Coulomb fit error × 1.0001e-11; 1e-11; 1e-11; 1e-10. Mutants on the polarized CAM-B3LYP cell at Gamma moved E by ≥ 1.1e-2 (α K on the β Fock, β K dropped, halved Madelung) and by 1.3e-3 to 2.5e-1 (ω ×1.01, ω ×0.01, c_lr := c_sr, c_sr ×0.99); on the 1×1×2 mesh by ≥ 1.9e-2 (also spin swap, unmixed Madelung, K from the total density). Only α-on-β and the spin swap are invisible in the closed-shell limit (2.2e-16). Limits: the attenuated G ≠ 0 kernel is exercised only by CAM-B3LYP (ω 0.33; for HSE06, ω 0.11, the largest erf weight on this cell is 4.2e-16 at Gamma and 1.4e-4 at 1×1×2, so its erf piece acts through the mixed Madelung constant and the semilocal ω); no open-shell wB97X pin; the bindings (Python run_uks_gamma / run_uks_kpts, the CLI UKS route at Gamma and on a mesh) call gamma_rsh::gamma_uks_any and solve_kuks, and the CLI energy equals the library to 1e-12 (HSE06, Gamma and 1×1×2, dense; the run log holds exactly the library value, and the 1-ulp residual the test sees at 1×1×2 is serde_json's float parse; RS-GDF measured 0 against a 1e-8 bar); open-shell wB97X (HYB_GGA_XC_WB97X) and any other libxc range-separated (CAM) GGA hybrid are accepted, not pinned; no range_split (the dense-AFT UKS force and the open-shell stress have their own rows; ROKS: previous row); the attenuated K is shared with the closed-shell code, so only PySCF is independent of the kernel; H/STO-3G fixtures only (no p or d shells)pbc_oshell_rsh.rs, validation_oshell_rsh.rs, gen_pyscf_oshell_rsh_refs.py, periodic_kpoint_open_shell.rs
Periodic k-point forces and stress (Hartree-Fock, library kpoint_rhf_gradient/kpoint_uhf_gradient/kpoint_rhf_stress/kpoint_uhf_stress): open-shell s+p on the fitted (RS-GDF) path, the dense H4-triplet supercell, and with a LANL2DZ ECP plus a rotating basis the range-split RS-GDF, the fit-acting force mutants and a shifted (Monkhorst-Pack) meshH2 triplet (2α/0β) and H4 triplet (3α/1β), H STO-3G s + one p, cubic cells, Gamma-centred 1×1×1 and 1×1×3, exxdiv none and ewald; HI (H cc-pVDZ, I s/p general contractions, LANL2DZ ECP) in diag(6, 6, 7), Gamma-centred 1×1×3 and MP 1×1×2, exxdiv none1×1×1 = Gamma (the Gamma code; independent in the LR part and the phase / residue binning, the SR derivative walks are shared); k-mesh = explicit 3-cell supercell / 3; central finite differences (h = 1e-4) of ferric's own k energy; range-split = unsplit fit; rotated = unrotated walkRS-GDF open shell: 1×1×1 − Gamma total ≤ 8.8e-13 (stress), 3.1e-13 (force), per part ≤ 9.9e-12; 1×1×3 − supercell/3 ≤ 5.0e-12 (H2), 8.2e-11 (H4) stress, ≤ 3.0e-11 force; FD (H2, all nine strains) 2.5e-8 stress, 2.5e-9 force, FD(2h)/FD(h) 4.04. Dense H4 1×1×3 − 12-atom supercell/3: 5.5e-13 (stress). HI: range-split − unsplit 5.2e-14 force, 1.3e-12 stress; FD of the split energy 3.4e-9 / 2.7e-8; RS-GDF FD force 3.5e-9; MP mesh FD 3.2e-9 / 1.2e-8, rotated − unrotated 1.5e-14 / 5.6e-131e-11 (1×1×1 total), 1e-10 (part), 1e-9 (supercell), FD 3e-8 force / 3e-7 stress (H) and 4e-8 / 3e-7 (HI), 2e-11 (split), 1e-11 (rotation), 6e-12 (dense H4). Every mutant that can act misses its anchor by ≥ 1e-4 (H supercell / Gamma anchors, smallest 8.7e-3) or moves the HI analytic total (all components) by ≥ 1e-5 (smallest 2.1e-4, SplitGammaKernel); the smallest overall is NoAuxFt 3.4e-5 (bar 1e-5, visible to the FD only); EcpMeshCountModuli, WrongQ and GammaMadelung are identities on the C2 system (0). Newly reachable: FitCrossSpin on a p-shell / empty-β density (≥ 1.98e-1 at 1×1×1, ≥ 6.2e-1 at 1×1×3), the fit-acting force mutants with an ECP (RotFitWrongResidue 1.5e-1, RotSwapPair 2.5e-2, FitAuxPhase 1.7e-3), and the shifted-mesh ECP residue mutants (EcpMeshCountModuli 2.0e-1 force, 8.0e-1 stress; identities on Gamma-centred meshes). Limits: the k RS-GDF derivative imports the Gamma SR walks (sr2_force, sr3_force, sr2_strain_with, sr3_strain_with, aux_ft_strain_shells), so 1×1×1 = Gamma and supercell anchors check only the LR part and the binning independently; that shared SR kernel is anchored by FD on the H2 triplet (both exxdiv), the H4 triplet (exxdiv none: all nine strains 7.2e-8, force 7.5e-10, FD(2h)/FD(h) 3.78–4.28, bar 1e-6 / 3e-8) and the H3 s+p quartet (3α/0β, exxdiv none: zz, xx, xy, xz 4.5e-8, force 2.6e-9, ratio 3.86; its 1×1×1 − Gamma 1.1e-12 stress, 4.2e-13 force); the HI mutant movers are measured on the analytic total, while the FD anchors cover a subset of components; orthorhombic cells only for the split ECP anchor; the H4 RS-GDF supercell floor is 16× the H2 one, attributed to the 4-H aux metric but not tested. Open-shell ECP, several ECP elements, other multiplicities, the 1×1×3 triclinic ECP mesh, other shifted meshes and the external (PySCF) force comparison are in the next rowvalidation_kstress_followups.rs, pbc_kstress.rs, pbc_kpoint_ecp_rotation.rs, validation_pbc_kstress.rs, validation_pbc_kgrad.rs, validation_periodic_kpoint_optimize.rs
Periodic k-point forces and stress (Hartree-Fock): an open-shell septet with two ECP elements, the 1×1×3 triclinic ECP mesh, shifted meshes beyond 1×1×2, and k-point forces against PySCFAsSb septet (8α/2β, As and Sb LANL2DZ ECPs, rotating s/p general contractions, s/p/d aux) in diag(6, 6, 10), Gamma-centred 1×1×1 and 1×1×3, k-UHF RS-GDF; HI (H cc-pVDZ, I LANL2DZ) triclinic rows (6,0,0), (1,6,0), (1.2,−0.8,7) at Gamma-centred 1×1×3, MP 2×2×1 on rows (8,0,0), (1.3,8,0), (1.2,−0.8,7), MP 1×1×4 and MP 1×1×3 on diag(6, 6, 7), k-RHF RS-GDF, rotation on; H2 STO-3G and s+p triplet, cubic 4 Bohr, dense AFT, Gamma-centred 1×1×3 (exxdiv none and ewald) and MP 1×1×21×1×1 = Gamma; 1×1×3 = explicit 3-cell supercell / 3; central FD (h = 1e-4) of ferric's own k energy; rotated = unrotated walk; MP 1×1×3 = Gamma-centred 1×1×3 (the same k set: an odd MP axis is not shifted); PySCF 2.13.1 pbc.grad.krhf / pbc.grad.kuhf forces (FFTDF 41³, all-electron gate lifted, per-k aufbau pinned for KUHF; the lifted gate checked against FD of PySCF's own energy, 7e-11..3e-9) and PySCF AFTDF energies with lanl2dzAsSb: 1×1×1 − Gamma 2.4e-14 force, 2.9e-13 stress; 1×1×3 − supercell/3 9.6e-14 force, 1.5e-12 stress; FD (all nine strains) 5.6e-8 stress, 7.8e-10 force. Triclinic HI 1×1×3: supercell 2.8e-13 / 8.1e-13, FD 2.9e-9 / 2.3e-8. MP 2×2×1: FD 1.2e-9 / 3.4e-8, rotated − unrotated 2.6e-14 / 1.3e-12; MP 1×1×4: FD 3.5e-9 / 2.6e-8, rotated − unrotated 5.3e-14 / 4.7e-13; MP 1×1×3 − Gamma-centred 1.9e-13 / 1.1e-12. vs PySCF: energies ≤ 6.1e-13, forces ≤ 2.5e-11 (RHF none/ewald, MP 1×1×2, s+p triplet UHF); ECP-cell energies 6.4e-6 (HI) and 3.1e-5 (AsSb septet)5e-12 (1×1×1), 3e-11 (supercell), FD 1e-8 / 1e-6 (AsSb) and 4e-8 / 3e-7–4e-7 (HI), 2e-11 (rotation, MP identity), PySCF 1e-11 energy / 3e-10 force, 3e-4 ECP energy. Every listed production-wired mutant misses its anchor or moves the total by > 1e-5, except WrongQ on MP 1×1×2, asserted as an identity (all q ≡ −q there); smallest EcpWrongResidue 2.6e-5 (AsSb force, supercell) and 3.1e-5 (MP 2×2×1 force); EcpCentreWrongAtom (centre derivative folded onto the next atom: here the other ECP atom; it also fires on single-ECP cells by folding onto H, so no mutant is specific to several ECP centres) 2.7e-3–6.9e-3; FitCrossSpin ≥ 7.9e-1; EcpMeshCountModuli 1.8e-3–2.1e-1 (force) and 2.5e-2–8.3e-1 (stress) on the shifted meshes, exactly 0 on MP 1×1×3. Limits: no external stress anchor exists (PySCF 2.13.1 has no k-point HF stress; its krks_stress is FFTDF pure-DFT only); no external force with an ECP (PySCF's k-point HF gradient takes GTH pseudopotentials only, ferric has none), so the ECP derivatives are anchored internally and the ECP cells only by energy; the external force pins rest on a one-line gate lift in PySCF (its own pseudo branch run for an all-electron cell); the AsSb−PySCF energy gap (3.1e-5, 5× the HI one) is not separated; the RS-GDF energies of the ECP cells carry a fit error vs dense of 2.9e-2 (HI) and 9.6e-3 Ha (AsSb), recorded by ecp_cell_dense_energies_match_pyscf (the anchors differentiate the fitted energy); with ECPs the high multiplicity is the closed-subshell septet (an IBr quartet/quintet with partly filled degenerate p shells did not converge in k-UHF); the all-electron H3 s+p quartet is in the previous row; the MP 2×2×1 k-RHF limit-cycles on the 6 Bohr triclinic rows, so it runs on 8 Bohr rowsvalidation_kstress_coverage.rs, gen_pyscf_kforce_pins.py
Periodic k-point Kohn-Sham (RKS: LDA, PBE, PBE0, SCAN, r2SCAN; UKS: LDA, PBE, PBE0; ROKS: PBE, PBE0) and ROHF analytic forces and stress on a cell whose basis carries an ECP, dense AFT and RS-GDFHI (closed shell) and the H2I doublet in diag(6, 6, 7) Bohr, H STO-3G + compact I s/p, LANL2DZ ECP on I; Gamma-centred 1×1×3 (non-TRIM), atom grid (20, 50), shared 16 Bohr neighbour cutoffcentral finite difference of ferric's own k-point energy (fresh SCF per displaced or strained cell): every force component, all nine strains for RKS LDA/PBE, UKS PBE and ROHF on dense J/K, three strains (zz, xy, xz) for the rest; the Python bindings (all four routes, 1×1×2) against FD of their own energies; ten production-wired ECP mutants (ECP term dropped, centre derivative dropped / sign-flipped / on the wrong atom, image weight at the transposed residue, home ECP image only, ket image weight) against the same FDforce ≤ 4.2e-10 Ha/Bohr, dE/dε ≤ 2.1e-9 Ha over all routes (the ECP parts are 6e-2 to 1.6e-1 and 1.5e-1 to 3.9e-1); every mutant misses by ≥ 3.1e-3 (force) and ≥ 2.1e-2 (stress) on RKS PBE (dense and RS-GDF), UKS PBE and ROHF (dense); Python bindings force ≤ 3.0e-10, two strains ≤ 6.6e-9. The strain FD uses h = 1.25e-5: the atom grid's hard neighbour cutoff puts one-step energy jumps (1e-11 to 1e-9 Ha) inside ±2.5e-5 on some components at 14, 16 and 20 Bohr cutoffs (FD misses of 6e-7 to 1e-5 that scale as 1/h, not h²); at 1.25e-5 every probed component is ≤ 2.1e-9, and a straddled jump would fail the anchor, not pass it3e-9 Ha/Bohr; 3e-8 Ha; mutants ≥ 1e3 × the bar. Not covered: a triclinic cell or shifted mesh with KS and an ECP (covered for HF in pbc_kpoint_ecp.rs / pbc_kpoint_ecp_rotation.rs, the same ECP walk), d/f shells on the ECP atom, more than one ECP atom, an external reference, B3LYP, SCAN/r2SCAN stress on RS-GDF with an ECP (accepted, not anchored: the ECP and RS-GDF SCAN stress anchors are separate), CLI numerics (the CLI tests check only that ECP cells pass the gate and reach the SCF)validation_kks_ecp_derivs.rs, pbc_kks_ecp_derivs.rs, test_pbc_kpoint_open_shell_and_stress.py
Periodic parallel, column-rotated and pair-symmetry-reduced walks are BITWISE their reference walks: RS-GDF SR/LR sums and k residue bins, Gamma and k-point SR nuclear attraction, the RS-GDF force and stress walks (Gamma RHF/UHF, k-point RHF, split), the overlap/kinetic and ECP force terms and the LR force pass across rayon thread counts and against frozen serial walks; the s2 (-r_L) k-pair bins against the ordered walk; the SR column rotation against the unrotated buildH2 / H3 / H4 cells, STO-3G to cc-pVDZ with ET and cc-pVDZ-RI aux, Gamma and 1×1×2 to 2×2×2 meshes, 1/2/6 threads (weekly); shrunk fixtures at 1/2 threads per commitfrozen serial walks and ferric's own unrotated / unsymmetrised builds (independent constructions of the same sums); the s2 and rotation mutants against the same buildsbit-for-bit (0 differing bits)0 bits; mutants must move the result. Per commit runs one shrunk canary per walk family (*_canary tests in the fast files); the full thread sets, larger cells and every mutant run weekly. Not covered: thread counts above 6, MPI ranksvalidation_pbc_parallel_bitwise.rs, pbc_parallel_bitwise.rs, validation_pbc_lr_force_bitwise.rs, pbc_lr_force_bitwise.rs, validation_pbc_kpair_symmetry.rs, pbc_kpair_symmetry.rs, validation_pbc_sr_rotation.rs, pbc_sr_rotation.rs

A "—" means the repository states no number for that cell. It is left empty on purpose rather than filled with an estimate.

Benchmark sweeps (GW100 and others) are kept in the project's working notes and are not reproduced here. Quoting a benchmark statistic from memory rather than from the record is the kind of unchecked claim this page exists to prevent.

Mixed-precision GPU kernels

[precision] mode = "mixed" (deprecated alias: [gpu] precision = "mixed") is opt-in (the default is f64) and applies only to the kernels in mixed_kernels. A mixed result is not Proven: it is not compared with an independent code, and it is not an f64 result. It is graded as a measured error map: a deterministic bound derived from the operation count, and the errors measured against the same run's f64 CPU energy on the systems below. Whether that error is acceptable for a given use is not decided here.

rimp2-energy (shipped). B_ov is stored on the device as f32 (half the bytes of the f64 tensor); each block G_i = B_iᵀ·B_tail is formed by f32 panel products of width b = 64 (the shipped default) summed into an f64 accumulator; the pair arithmetic, denominators and all sums are f64. A finite B_ov element beyond the f32 range, or one above sqrt(f32::MAX / b) (an f32 panel sum of products could overflow), or an unavailable flush kernel, runs the f64 device path instead and is counted (mixed_fallback_f64); a NaN B_ov passes through to a NaN energy as on the CPU; a pool too small for the f32 tensor runs the CPU path unchanged.

ColumnValue
Accumulation depthn = naux per element, panels of b = 64
κ_summeasured per system (table): the energy-level κ_E = Σ fac|g|S/|D| / |E_os| and the p99 of the element κ = S_ab/|g_ab| on the first block (the element maximum is unbounded because g_ab crosses zero)
Boundper element ε_G·S_ab + η with ε_G = (1+u₃₂)²(1+γ_b(u₃₂))(1+γ_⌈naux/b⌉(u₆₄)) − 1 + γ_naux(u₆₄) (u₃₂ = 2⁻²⁴, u₆₄ = 2⁻⁵³; the last term is the f64 reference's own rounding) and η the f32 underflow term; summed into an energy bound through the OS and SS terms
Mitigationf64 panel accumulation, f64 pair arithmetic, f64 everything else
Precisionf32 storage, f32 panel products, f64 sums

The measured error against the f64 CPU energy of the same B_ov at b = 64 (E_corr is the f64 RI-MP2 correlation energy; every reference is an RI-JK RHF with loose thresholds, the same orbitals on both sides of each comparison). OS and SS are signed and partly cancel in the total, so all three are given:

System (frozen core)nocc / nvir / nauxE_corr (Eh)ΔE_OS (Eh)ΔE_SS (Eh)ΔE total (Eh)total (kcal/mol)bound OS / SS (Eh)κ_Ep99 κ
H2O / cc-pVDZ5 / 19 / 84−0.204009−4.9e-9−2.1e-9−7.0e-9−4.4e-61.6e-6 / 2.8e-61.304.8e11
n-butane / cc-pVDZ17 / 89 / 364−0.599582−2.8e-10−3.7e-10−6.5e-10−4.1e-77.2e-6 / 1.4e-51.954.6e11
n-octane / cc-pVDZ33 / 169 / 700−1.183770+1.1e-10−8.3e-11+2.6e-11+1.6e-81.7e-5 / 3.3e-52.402.0e11
n-dodecane / cc-pVDZ49 / 249 / 1036−1.767936−1.7e-10−9.1e-11−2.6e-10−1.6e-72.9e-5 / 5.6e-52.761.2e11
H2O / aug-cc-pVTZ5 / 87 / 198−0.283553−6.1e-10+3.7e-10−2.4e-10−1.5e-72.5e-6 / 4.6e-61.501.2e11
benzene / aug-cc-pVDZ (6)15 / 171 / 570−0.810690+5.1e-10+4.7e-10+9.7e-10+6.1e-77.8e-6 / 1.4e-51.652.1e12
benzene / aug-cc-pVTZ (6)15 / 393 / 912−0.963520−8.7e-10−1.0e-10−9.7e-10−6.1e-71.0e-5 / 2.0e-51.843.0e12

Every component is inside its bound. The error splits into an f32-storage part (B_ov rounded to nearest through f32, recomputed in f64) and an accumulation part (the device minus that), each inside its own bound; at benzene / aug-cc-pVTZ the accumulation part (OS −9.4e-10) is the larger.

What the bound can and cannot catch. The energy bound is a worst case over rounding signs, three to six decades above the measured energy errors (at the worst single element the error reaches 7.1e-2 of its per-element bound at benzene / aug-cc-pVTZ and up to 1.2e-1 at dodecane, but those elements carry little of the energy). It catches gross defects, such as a wrong operand offset, a dropped panel or a dropped pair weight (1e-1 relative error). It does not catch a subtle degradation of the kernel: a plain SGEMM without the f64 flush, an f32 B_ov with an f64 GEMM, a truncating instead of round-to-nearest upload, or f32 accumulation across panels all stay inside it. They are pinned by other checks:

  • The rounding mode is guaranteed by a bit-exact test: the device f32 B_ov equals as f32 (round to nearest, ties to even) element for element.
  • The f64 flush across panels is guaranteed by a deterministic construction (panel sums +2²⁰, 2⁻⁹, −2²⁰ give exactly 2⁻⁹ on the device; summing the panels in f32 gives 0).
  • The panel structure is checked by a measurement over every G block of benzene / aug-cc-pVTZ (18.3 million elements): the per-element RMS of err/S is 1.80e-8 for the device and 1.78e-8 for the host twin of the same algorithm (ratio 1.007), against 2.79× the twin for a plain SGEMM, 0.24× for an f32 B_ov with an f64 GEMM, 1.29× for a truncating upload and 1.30× for f32 accumulation across panels. The accepted device/twin ratio lies between 0.49 and 1.13 (the geometric midpoints to the nearest measured defect on each side). This measurement is supplementary and device and system dependent: its margin (about 13%) is of the size of the device/twin spread between systems (0.87 at water / cc-pVDZ, 1.007 at benzene / aug-cc-pVTZ), so it is not a guarantee on another device or build. Measured against the shipped b = 64, a panel width of 96, 128 or 256 reads 1.27, 1.51 or 2.39 (above the band); a width of 32 reads 0.70 (inside it: a more accurate kernel, a performance difference that is not pinned).

How the error depends on size. The per-element RMS of err/S falls with the auxiliary depth over the four cc-pVDZ systems: 4.9e-8, 2.5e-8, 2.1e-8, 1.8e-8 at naux 84, 364, 700, 1036; the fitted slope of ln RMS against ln naux is −0.40 ± 0.03 (2 degrees of freedom; one-sided 95% upper limit −0.32; the random-sign model predicts −0.5). This describes the shipped kernel; it is not a law and not a defect gate: naux is confounded with the molecule (without water the slope is −0.31), and a plain-SGEMM or truncating-upload emulation on the same blocks also falls with naux. No size dependence is claimed for the total energy error: its signed OS and SS parts scatter by two decades across systems through cancellation. Test files: crates/ferric-mp2/tests/rimp2_f32_storage_error_law.rs (no device; the name predates this wording) and crates/ferric-mp2/tests/gpu_rimp2_mixed.rs (device).

rimp2-energy, unrestricted (shipped, same kernel). The unrestricted (open-shell) Coulomb RI-MP2 energy keeps one f32 B_ov per spin on the device and forms every block of E_αα, E_ββ and E_αβ with the same f32 panel products of width b = 64 and f64 accumulation; the antisymmetrised same-spin K = g_ab − g_ba, the opposite-spin g², the denominators and all sums are f64. The refusals and the mixed_fallback_f64 counter are the closed-shell ones. The erfc and terfc unrestricted energies use the same kernel (next paragraph); every other caller of the unrestricted pair kernels (the erf operator, composite fitted operators, and direct calls of the pair kernels) runs the f64 device kernel.

ColumnValue
Accumulation depthn = naux per element, panels of b = 64
Boundthe closed-shell per-element factor ε_G·S_ab + η, propagated through the SS term (K = g_ab − g_ba, perturbation δ_ab + δ_ba) and the OS term (g²), plus the f64 summation of the terms
Acceptance|ΔE| ≤ 1.0e-3 Eh per heavy atom and per electron; the derived bound itself is below it on every system below
Mitigationf64 panel accumulation, f64 pair arithmetic, f64 everything else
Precisionf32 storage, f32 panel products, f64 sums

The measured error against the f64 CPU energy of the same B_ov at b = 64 (cc-pVDZ / cc-pVDZ-RI, all electrons correlated; every reference is an RI-JK UHF with loose thresholds, the same orbitals on both sides):

Systemnocc α/βnvir α/βnauxE_corr (Eh)ΔE_αα (Eh)ΔE_ββ (Eh)ΔE_αβ (Eh)ΔE total (Eh)per heavy atomper electronbound (Eh)bound per heavy atom
O2 triplet9 / 719 / 21112−0.348888−2.4e-9+1.1e-8+5.5e-9+1.4e-87.0e-98.7e-103.9e-62.0e-6
CH3 doublet5 / 424 / 2598−0.129040+1.5e-10+2.3e-10+1.3e-10+5.0e-105.0e-105.5e-111.6e-61.6e-6

Every block is inside its bound (largest error/bound 1.2e-2, O2 E_ββ). As in the closed-shell case the energy bound is blind to a subtle kernel degradation: a build that accumulates the whole contraction in f32 (one panel) stays inside it on both systems (O2 ΔE +9.2e-9 Eh). That degradation is caught by a deterministic construction through both the same-spin and the opposite-spin blocks (an element of exact value 1 + 2⁻³⁰ split over two panels: the device returns the f64 energy exactly; an f32 sum loses 2⁻²⁹ Eh, against an accepted error of 2⁻³⁰) and by the panel counter (⌈naux/b⌉ panels per block). Test files: crates/ferric-mp2/tests/gpu_u_rimp2_mixed.rs and crates/ferric-mp2/tests/gpu_u_rimp2_mixed_pin.rs (which callers may use the kernel).

rimp2-energy, erfc and terfc operators (shipped, same kernel). The RI-MP2 energy under the primitive erfc and terfc operators, closed-shell and unrestricted, runs the same kernel: plain and attenuated RI-MP2 (att-rimp2, dense and QQR-screened), SCS-MP2 with two terfc operators and the MP2 half of att-MP2+VV10. The per-element factor is the Coulomb one (a property of the arithmetic), but S_ab = Σ_P |B_P,ia||B_P,jb| and the underflow term are read from that operator's own dressed B_ov, whose metric is the attenuated (P|Q); a worse-conditioned attenuated metric would appear as larger |B| and larger S_ab / |g_ab| and is carried element by element. The bound assumes the reference is the f64 energy of the same f64 B_ov: the attenuated RI fitting error is common to both and is not part of it. The erf operator, composite fitted operators (terfc_fit), SR-MP2 inside RS-MP2+RPA and OO-MP2 (the energy kernel inside a larger algorithm whose sensitivity to this error was not analysed) and kappa-regularised runs stay on the f64 device kernel.

Measured at the repository defaults (erfc ω = 0.420 Å⁻¹, terfc r₀ = 1.05 Å), cc-pVDZ / cc-pVDZ-RI, all electrons correlated, against the f64 CPU energy of the same B_ov (RI-JK RHF / UHF references). At these parameters the dressed B_ov is as well scaled as the Coulomb one (water: max|B| 0.209 erfc, 0.211 terfc, 0.201 Coulomb; κ_E 1.306, 1.305, 1.304):

SystemOperatornauxE_corr (Eh)ΔE total (Eh)per heavy atomper electronbound (Eh)bound per heavy atom
H2Oerfc84−0.198624−1.7e-91.7e-91.7e-104.3e-64.3e-6
H2Oterfc84−0.203297+6.1e-96.1e-96.1e-104.3e-64.3e-6
O2 tripleterfc112−0.337446−2.2e-91.1e-91.4e-103.8e-61.9e-6
O2 tripletterfc112−0.346018−4.5e-92.3e-92.8e-103.9e-62.0e-6
CH3 doubleterfc98−0.122053−6.5e-116.5e-117.2e-121.5e-61.5e-6
CH3 doubletterfc98−0.126625−1.8e-91.8e-92.1e-101.6e-61.6e-6

Every component (OS and SS, or αα, ββ and αβ) is inside its bound, and the f32-storage part of the error is above the f64 device gate on every one, so a caller that must stay f64 is told apart from a mixed one. These are small systems at moderate attenuation; a strongly attenuated or poorly conditioned metric is not measured here. Test files: crates/ferric-mp2/tests/gpu_rimp2_mixed_attenuated.rs (needs FERRIC_TERF_TABLE_DIR for the terfc rows) and crates/ferric-mp2/tests/gpu_u_rimp2_mixed_pin.rs.

xc-vxc (opt-in, not in the default mixed set: it runs only when mixed_kernels names it, under mode = "on"; on a GTX 1080 the device XC path, f64 or mixed, was slower than the CPU, so there is no reason to run it by default). It has run on hardware: on a GTX 1080 (Pascal, sm_61) gpu_xc_mixed.rs passes (device error in the twin's class, no f64 fallback), with 2 extra SCF iterations on benzene and |ΔE| ≤ 3.4e-13 Eh. The Kohn-Sham XC potential matrix of LDA and GGA functionals (closed- and open-shell; hybrids included, their exact exchange is untouched): each grid batch's V_b = A·Xᵀ is formed on the device from f32 A (weighted potential factors) and f32 X (basis-function values and gradients, stored once per grid as f32 next to the f64 copy) by f32 panel products of width b = 64 summed into an f64 accumulator. The density pass, the functional, E_xc and the V accumulation are f64. Meta-GGAs never use it (meta-GGA τ is on the never-f32 list; the device pass refuses mixed whenever τ is present, independently of the caller). A precision ladder decides per Fock build: a build is mixed only when the RMS change of some spin density since the previous build of the same SCF exceeds τ = 1e-5; the first build of every SCF (the ladder is reset when an SCF starts) and any one-shot V_xc are f64. At a converged exit whose last build was mixed, that build's V_xc is replaced by an f64 rebuild at the same density before the Fock matrix is diagonalised, so the Fock matrix and orbitals a converged SCF returns come from f64 builds whatever density_conv is and whichever exit accepted it (the energy needs no rebuild: E_xc comes from the f64 density pass and functional). A wave whose panel sums could leave the f32 range, or whose A does not round to f32, runs f64, and a missing flush kernel, an AO value beyond the f32 range or a pool too small for the f32 copies keep the grid f64 on the device; all are counted (mixed_fallback_f64).

ColumnValue
Accumulation depthk = ncols per element (the batch's point count, ≤ 128 by default), panels of b = 64
Boundper element |ΔV_ij| ≤ (2u₃₂ + γ_b(u₃₂) + γ_⌈k/b⌉(u₆₄))·(|A||X|ᵀ)_ij, plus the f64 reference's own γ_k(u₆₄); worst measured element 0.10 of it (water / cc-pVDZ, every 7th batch, with synthetic sine-wave potential factors in A, not functional output)
Energysecond order: the SCF energy is variational in the density, and only the Fock matrix of unconverged iterations carries the error
Mitigationf64 panel accumulation, f64 density pass and functional, f64 closing iterations (the ladder), and an f64 V_xc rebuild at a converged exit whose last build was mixed
Precisionf32 operands, f32 panel products, f64 sums

Why the ladder. With every build mixed, the f32 panel sums act as rounding noise that changes with the density, and a tight SCF stalls on it: benzene / cc-pVDZ PBE took 77 instead of 12 iterations at density_conv = 1e-9 and did not converge in 200 at 1e-10. Rounding only the basis-function values (a fixed perturbation) costs nothing; rounding only A costs 2 to 6 iterations; the panel accumulation is the rest. τ = 1e-3, 1e-4 and 1e-5 kept 3-4, 4-5 and 6 of about 10 builds mixed for at most 3, 3 and 2 extra iterations.

The measured effect on converged SCF energies (host twin of the kernel, the same algorithm on OpenBLAS, against the f64 SCF of the same input; default SCF and grid settings; measured before the f64 rebuild at a converged exit existed, which by construction changes neither: it runs after the exit decision and E_xc never depends on the V_xc precision; not re-measured):

Systembasisfunctionaldensity_conviterations f64 / mixedmixed buildsΔE (Eh)
H2Occ-pVDZPBE1e-69 / 96−2.8e-14
H2Occ-pVDZPBE1e-1012 / 126+1.4e-14
H2Oaug-cc-pVTZPBE1e-69 / 96+2.1e-13
H2Oaug-cc-pVTZPBE1e-1013 / 136+9.9e-14
H2Occ-pVDZr2SCAN (meta-GGA: f64)1e-1013 / 1300
H2O⁺ (UKS)cc-pVDZPBE1e-69 / 96+1.4e-14
H2O⁺ (UKS)cc-pVDZPBE1e-1012 / 1360
benzenecc-pVDZPBE1e-610 / 106−6.3e-13
benzenecc-pVDZPBE1e-1013 / 156−5.1e-13
benzenecc-pVDZB3LYP1e-69 / 96+4.5e-13
benzenecc-pVDZB3LYP1e-1013 / 156−2.8e-13
n-octanecc-pVDZPBE1e-611 / 117+9.1e-13
n-octanecc-pVDZPBE1e-1018 / 187−9.1e-13

These are host-twin figures: the device kernel's own agreement with the twin (same error class, within a factor 10 on max|ΔV|) and a device SCF against the f64 host SCF are checked by crates/ferric-dft/tests/gpu_xc_mixed.rs, which needs a device; it passes on a GTX 1080 (sm_61). Test files: crates/ferric-dft/src/xc_batch.rs (element bound, no device), crates/ferric-scf/tests/xc_mixed_vxc_error_map.rs (this table, and the converged-exit Fock matrix against an independent f64 rebuild at a loose density_conv, RKS and UKS, no device).

dfk-occ (opt-in, not in the default mixed set: it runs only when mixed_kernels names it, under mode = "on"; on a GTX 1080 a mixed SCF saved about 0.7 s (4%) of the wall time against the f64 device DF-K at def2-TZVP, nothing measurable at def2-SVP, and was 3-6% slower with diffuse basis sets, so there is no reason to run it by default). The DF-K occupied path with the dressed B resident on the device as f32 and C_occ rounded to f32 on every build: each Y_P = C_occᵀ·B_P is a k-panelled f32 product of width b = 64 flushed into f64, and the SYRK and everything after it are f64. It runs behind the SCF precision ladder (crates/ferric-scf/src/jk_ladder.rs), shared with dfj-pack: a build is mixed only while the density moves by more than τ = 1e-5 and still contracts (one way: once it settles or stops contracting the SCF stays f64), a build the ladder keeps f64 runs the f64 device path on an f64 copy of B held beside the f32 one (rounded from it on the device, bit-identical to rounding on the host; when the pool cannot hold both copies, the f32 copy alone and the host f64 path), and a converged exit whose last build was mixed is refused and the iteration redone in f64. The energy is first-order in the K error, so that f64 exit, not a Fock-matrix swap, is what makes the returned energy and density f64 builds. A B element beyond the f32 range or a flush kernel that will not load runs the f64 device upload instead, counted (mixed_fallback_f64).

ColumnValue
Accumulation depthstage 1: k = nbf per element, panels of b = 64; stage 2 (f64): k_chunk + nchunks
Bound|K̂ − K| ≤ ε·S, S = Σ_{P,i} (|B_P||C|)(|B_P||C|)ᵀ, ε = k_error_factor_mixed (two f32 operand roundings, f32 panel sums, f64 flush), plus the f64 reference's own factor (crates/ferric-scf/src/df_k_gpu.rs)
Energyfirst order in K, so a converged SCF's energy comes from f64 builds (the ladder's f64 exit); only unconverged iterations carry the mixed error
Mitigationf64 panel flush, f64 SYRK, the precision ladder and its f64 exit
Precisionf32 B and C_occ, f32 panel products, f64 sums

The measured effect on converged SCF energies: the device SCF with dfk-occ mixed against the f64 device SCF of the same input (RI-JK, def2-universal-jkfit, [gpu] mode = "on", default SCF settings, GTX 1080, the release CLI). "f64 exit |dE|" is the f64 run's energy change at its last iteration, the scale of its own termination noise:

Systembasismethodatomsnbfiterations f64 / mixedmixed K builds / total|ΔE| (Eh)|ΔE| per atom (Eh)f64 exit |dE| (Eh)
H2Occ-pVDZRHF32410 / 107 / 224.3e-141.4e-141.1e-11
n-butanedef2-SVPRHF1410610 / 106 / 211.1e-138.1e-152.2e-11
n-octanedef2-SVPRHF2620210 / 105 / 210 (bit-identical)06.5e-11
n-dodecanedef2-SVPRHF3829810 / 105 / 211.4e-123.6e-145.9e-11
n-hexadecanedef2-SVPRHF5039410 / 105 / 212.3e-124.5e-149.0e-11
n-icosanedef2-SVPRHF6249010 / 105 / 213.2e-125.1e-141.1e-10
benzeneaug-cc-pVDZRHF1219211 / 126 / 236.8e-135.7e-142.8e-13
benzeneaug-cc-pVTZRHF1241410 / 126 / 253.4e-132.8e-147.5e-12
OH (UHF)cc-pVDZUHF21910 / 107 / 222.8e-141.4e-142.2e-10
OH (UKS)cc-pVDZB3LYP21924 / 236 / 355.7e-142.8e-145.7e-14
benzenedef2-SVPωB97X (RSH)121149 / 96 / 201.1e-139.5e-155.0e-10

The largest |ΔE| is 3.2e-12 Eh, against the 1.0e-3 Eh shipping bar, with no f64 fallback in any run. It is at or below the f64 run's own exit |dE| in every row but benzene / aug-cc-pVDZ (6.8e-13 against 2.8e-13, both at the 1e-12 scale of termination noise), so what it measures is the two runs' termination noise, not the kernel's error. The slope of ln |ΔE| against ln N is 2.29 ± 0.13 (standard error, 2 degrees of freedom) over the four alkanes with a nonzero |ΔE| and 1.27 ± 0.17 (8 degrees of freedom) over the ten such systems; it is the slope of that noise floor (the f64 exit |dE| grows with N too), not a growth law of the mixed error, which the ladder's f64 exit removes from the converged energy. With diffuse basis sets the mixed builds' own error holds the density change between builds at 5e-5 to 1e-4, above τ, while the DIIS error keeps falling. The ladder is therefore one way: the first build after a mixed one that finds the density settled (changed by at most τ) or not contracting (a change at least the previous build's) latches the SCF to f64 for good. Before the latch the ladder kept returning to mixed and DIIS stalled near τ: benzene / aug-cc-pVDZ took 44 to 103 iterations (the count depended on the last bits of the f64 K), and aug-cc-pVTZ 20, against 11 and 10 in f64; with it, 12 and 12.

Speed, n-dodecane RHF (RI-JK, the same card, 6 Rayon threads, two interleaved repeats each, whole-run wall time):

basisnbfCPUdevice f64device mixed dfk-occ
def2-SVP2988.5, 7.2 s8.7, 7.5 s7.4, 7.5 s
def2-TZVP52822.2, 22.1 s18.5, 18.3 s17.5, 17.8 s

The def2-TZVP run spends 8.4-8.6 s in setup (the 3-index integrals and the V^-1/2 dressing of the DF-K tensor), the same in every arm, and 6.6 s (f64) against 6.0 s (mixed) building J and K over its 10 iterations. The first build, before there are occupied orbitals, factors the guess density's eigenvalues (314 of 528 kept) through the same occupied path. A mixed iteration took 0.47-0.48 s against 0.60 s, the iterations the ladder keeps f64 0.60-0.61 s (the f64 device path), and the first device build, which uploads the f64 B and rounds its f32 copy on the device, 1.02 s against 1.12 s. Holding both copies costs device memory: 4.58 GB of dressed B at def2-TZVP against 1.53 GB for the f32 copy alone and 3.05 GB in f64. On the benzene rows above the mixed SCF took 1.76-1.82 s against 1.66-1.75 s (aug-cc-pVDZ) and 4.9-5.0 s against 4.7-4.8 s (aug-cc-pVTZ).

The kernel itself (the derived K bound, the exact cross-panel flush and C_occ rounding pins, the multi-chunk build, the operand-class twin gate, every fallback) is checked by crates/ferric-scf/tests/gpu_dfk_mixed_kernel.rs, and the laddered SCF energy against a bar derived from the f64 floor by crates/ferric-scf/tests/gpu_jk_ladder.rs; both need a device and pass on a GTX 1080 (sm_61).

Known limits and negatives

  • Analytic Hessians: closed-shell RHF, UHF and ROHF only (exact J/K, basis up to f functions, no solvent, no polarizable embedding; ECPs for RHF, UHF and ROHF (also with RI-J and/or RI-K) and external potentials for RHF and UHF but not ROHF; RI-J and/or RI-K for RHF, UHF and ROHF without an external potential). UHF states are not checked for internal stability or for being the lowest state: an unstable or excited UHF solution gets the Hessian of that solution. PySCF has no ROHF Hessian, so the ROHF reference is a finite difference of PySCF's analytic gradient. ROKS and every other method's frequencies take central finite differences of the analytic gradient.
  • Local MP2: the integral-direct local MP2 (rimp2 with [local] integral_direct = true) has its correlation stage measured at about N1.24 (erfc) and N1.4 (Coulomb) on n-alkanes C20–C48 (6-31G / cc-pVDZ-RI, ε = 1e-4, frozen carbon cores, a calibrated pair gate; fitted to three points). That is one family of molecules in one basis: not shown to be linear, and not measured on 3-D or diffuse systems. Local MP2 without integral_direct still builds the global 3-index tensor and makes no scaling claim. See The MP2 family.
  • DLPNO-MP2 PNO truncation is canonical and not compared like for like: ferric's pair-driven DLPNO-MP2 with PNO truncation builds PNOs per canonical occupied pair (dlpno_mp2_from_b_ov divides by a diagonal eps). A separate localized-occupied path (dlpno_mp2_localized_from_b_ov) does Boys-localized, Fock-coupled pair screening only, with no PNO or PAO truncation; ORCA localizes the occupieds and truncates PAO domains. The recoveries above are therefore reported side by side and never ranked. There is no DLPNO-CCSD or DLPNO-CCSD(T) energy driver, so ORCA's DLPNO-CCSD(T) figure is context only. The canonical pair screen is uninformative for n-butane to n-octane; with Boys-localized occupieds ferric's pair screen drops pairs (retention 0.585 frozen-core and 0.41 all-electron at C8, t_cut_pairs = 1e-4; the all-electron figure includes core pairs that the canonical screen also drops), measured only for these chains and below the locality onset. Library only.
  • Exact dRPA by the Riccati solve is a small-system path: its ring-product plan holds no³·nv² numbers, no times the amplitudes themselves (C12 thrashed, then was killed for memory). The CLI and run_drpa refuse a run that cannot fit before the SCF; full-rank pdep-rpa gives the same energy at far lower memory.
  • Laplace SOS-MP2, AO-sparse variant: the domain truncation is accurate, and the radius it needs grows far more slowly than the molecule (chemical accuracy at 3 to 5 Bohr on n-alkanes C2 to C12, radius/diameter falling from 0.52 to 0.17; a 71-atom drug molecule is within 0.05% at 4 Bohr, about 13% of its diameter). The algebra is still dense, so no speedup is claimed. The regression test sos_ao_sparse_truncation_radius_is_transferable_across_sizes pins the STO-3G butane/octane comparison (12 Bohr exact on both; octane worse at 3 Bohr). The C2-C12 sweep and the drug-molecule figure are measurements, not regression tests.
  • TDHF/RPAx C6: ~63% low regardless of gap. Do not use it for dispersion. Its static α is implemented as specified (it matches an independent build of the same kernel, see anchors) but is not physically established: water/cc-pVDZ at PBE with a 0.36 Ha scissor gives 5.20 a.u. against DOSD 9.64 (−46%). A matching static α says nothing about the dynamic response that C6 needs.
  • TDDFT / TDA: closed-shell references only. Functionals without an fxc kernel (VV10, range-separated) are refused rather than run without it. B3LYP at aug-cc-pVDZ agrees with PySCF to 8.1e-4 eV on the default pruned grid and to 2.8e-6 eV on the reference's flat grid: the remainder is pruned-grid error. Meta-GGA excitations carry XC-grid error, so meta-GGA TDA/TDDFT defaults to a 150 × 434 grid: water r2SCAN TDA roots sit within 9.2e-5 eV (6-31G) and 5.5e-6 eV (aug-cc-pVDZ) of a flat 150 × 590 grid there, against 2.1e-3 eV on the 75 × 110 production grid (PBE: 4.4e-4 eV). SCAN is not grid-converged to 1e-4 eV on any grid tested: 3.6e-3 eV on the default, and flat 150 × 590 itself is 1.2e-3 eV from flat 200 × 590. See TDDFT.
  • MP2 analytic polarizability (library only): one analytic path, mp2_polarizability_analytic → analytic_alpha_relaxed (see anchors); frozen_mp2_c6_molecular and the attenuation sweeps use it. It and the relaxed dipole (static_relaxed_density_ao) refuse a frozen core. The frozen-amplitude C6 takes its frequency shape from HF α(iω), not from an MP2 dynamic response.
  • Periodic open-shell forces and stress refuse a non-stationary SCF state: a ROHF/ROKS orbital gradient or a k-point UKS spin commutator above 1e-5 Ha, a stored Fock more than 1e-8 Ha from the rebuilt one, or SCF k-points that are not the mesh's. Each tolerance and its provenance (measured, round-off or arbitrary) is in section 6 of crates/ferric-pbc/theory.md.
  • COSX gains at high angular momentum and does not gain with system size. Measured on one thread at the flat (50,110) grid with the overlap fit:
    • Against LinK on n-alkanes at def2-SVP it is slower at every size measured: 1.59× (C20), 1.09× (C32) and 1.22× (C48), with no trend toward parity. At def2-TZVP on C20 it is faster (0.67×); that is the only triple-zeta size measured.
    • Against exact direct exchange on butane it is 3.7× slower at def2-TZVP (full SCF). At def2-QZVP its K build takes 90 s against about 400 s for the direct build's single J+K sweep, at a relative K error of 5.4e-5.
    • Its K build grows as N1.29–N1.32 between C20 and C48 at def2-SVP.
    • It applies to the Coulomb operator only: a range-separated functional takes its exchange from density-fitted short- and long-range fitters and ignores k_builder = "cosx", with a warning.
    • Its analytic gradient is exact for RHF, RKS, UHF, UKS, ROHF and ROKS with the overlap fit off, and for RHF and UHF with the default overlap fit, on flat and pruned (sgx) COSX grids. Fitted COSX with a KS functional, and fitted COSX for ROHF, are refused for gradient tasks, before the SCF runs.

Why the distinction is drawn so sharply

A quantum chemistry code can produce a plausible number in many ways that are wrong:

  • a non-converged SCF returned as an ordinary result, because convergence is a flag rather than an error
  • a method missing a physical term it does not mention (for example, TDDFT excitations computed without the fxc kernel)
  • a fallback model silently substituted for one atom in a molecule, changing a partitioning without changing the shape of the output
  • a screening or truncation threshold that happens to be safe for the test system and not for yours

None of these look like failures, and every one of them has occurred in this codebase. The remedy is to grade each capability separately and say which ones are checked against ground truth.

Testing discipline

Some properties are pinned by tests rather than asserted in prose:

  • Bit-identity across thread counts for reductions and permutations: results do not depend on RAYON_NUM_THREADS
  • ERI 8-fold permutational symmetry: verified against the engine, not assumed, since the MP2 code relies on it to compute only ~1/8 of quartets
  • Size-extensivity and rotational invariance of total energies
  • Memory guards in both directions: a starved budget must be refused and an ample budget must still run, because an over-estimating guard is also a bug

New guards are mutation-tested: a defect is injected and the test confirmed to fail before the guard is trusted. This has caught guards that passed while proving nothing.

Input reference (TOML)

Every key the ferric CLI accepts, section by section.

Unknown keys are a hard error. The config structs in crates/ferric-cli/src/config.rs are #[serde(deny_unknown_fields)], so a misspelled key or section aborts the run before anything is computed. That includes [external_potential], its point charges, and each [[scf.ladder]] rung.

String values go through strict parsers, where an unknown value is an error at load time rather than a default. Most accept any capitalisation; the exceptions are noted per key.

This page is hand-maintained against crates/ferric-cli/src/config.rs at commit 4b64e6ce. Where a default is applied at the point of use rather than in config.rs, it was read from crates/ferric-cli/src/lib.rs at the same commit. If the code and this page disagree, the code wins. For which method.kind values exist and what each supports, see Capabilities and validation.

Units follow the code: [molecule] geometries are Å (XYZ), point charges and cutoffs named *_bohr are Bohr, keys named *_angstrom or documented as Å are Å, and energies are Hartree.

A minimal file:

[molecule]
xyz = "testdata/molecules/water.xyz"
[basis]
name = "cc-pvdz"
[method]
kind = "rimp2"
[mp2]
auxbasis = "cc-pvdz-ri"

[molecule] (required)

KeyTypeDefaultAllowed valuesNotes
xyzstringrequiredpathStandard XYZ in Å, relative to the working directory. Not read when [qmmm] is present (the PQR supplies the geometry).
chargeinteger0With [qmmm], applies to the QM region.
multiplicityinteger1≥ 1Read by uhf, rohf and ksdft (UKS) for every task, and for task = "energy" only by rimp2/oo-rimp2 (UHF + unrestricted RI-MP2 / OO-RI-MP2) and the open-shell path of pdep-rpa/gw/mp2-v: UHF, or UKS when [rpa] xc is set (pdep-rpa, gw), or ROHF/ROKS for gw with [gw] reference = "rohf"; mp2-v stays UHF. Every other kind refuses > 1. See open shells.

[basis] (required)

KeyTypeDefaultAllowed valuesNotes
namestring—a bundled name (case-insensitive)e.g. sto-3g, 6-31g*, 6-311++g**, cc-pvtz, aug-cc-pvqz, cc-pvdz-pp, def2-tzvpp, def2-qzvppd, pcseg-1; the full list of 64 sets and their aliases is in Bundled basis sets.
pathstring—path to a Gaussian-94 fileUsed only if name is absent. One of name/path is required.

Auxiliary basis names used elsewhere (auxbasis, df_*_aux) come from the same table: cc-pvdz-ri, cc-pvtz-rifit, aug-cc-pv{d,t,q}z-rifit, def2-svp-rifit, def2-tzvp-rifit, def2-tzvpp-rifit, def2-qzvp-rifit, def2-qzvpp-rifit, def2-universal-jkfit, cc-pvtz-jkfit, cc-pvqz-jkfit, cc-pvdz-f12-optri. cc-pvdz-rifit is an alias of cc-pvdz-ri; both names load the same set.

[method] (required)

KeyTypeDefaultAllowed valuesNotes
kindstringrequiredrhf uhf rohf ksdft rimp2 mp3 oo-rimp2 att-rimp2 mp2-v scs-mp2 scs-mp2-2terfc laplace-mp2 laplace-sos-mp2 pdep-rpa rs-mp2-rpa gw bse-tda tdhf-static-polarizability ccsd ccd ccsd(t) linlccd drpa wb97x-l-v b2plyp dsd-pbep86 tda tddftAny other value is an error. Smoke- and Spike-grade kinds print a [warning] grade line on stderr; Proven kinds and the ungraded laplace-sos-mp2 print none. rimp2, drpa and linlccd name the method and are computed exactly unless [local] sets a local approximation (see Capabilities and validation).
taskstring"energy"energy optimize frequenciesoptimize: rhf ksdft uhf rohf rimp2 pdep-rpa only. frequencies: rhf ksdft uhf rohf only.

[scf]

Read by every kind, because every kind runs an SCF first.

KeyTypeDefaultAllowed valuesNotes
max_iterinteger200Iteration cap; equals the library (RhfConfig) and Python default.
energy_convfloat1e-3Sanity bound, not a target. Convergence requires ΔP_rms < density_conv, ΔP_max < 10·density_conv and ΔE < energy_conv. ΔE has a noise floor that grows with system size, so a tight value is met only by chance and costs iterations; ferric prints a warning: [scf] energy_conv ... line when a value you wrote is below 0.3 times the estimated floor (see sharp bits); functionals, the LinK / COSX exchange builders, PCM / COSMO and double-hybrid kinds get no warning.
density_convfloat1e-6The convergence signal.
diis_sizeinteger8
diisstring"pulay"pulay adiis ediis (case-insensitive)An unknown value is an error when the file is loaded.
diis_switch_threshfloat1e-1Error level at which ADIIS/EDIIS hand over to Pulay. Ignored for pulay. If the density stops moving while the error is still above this level, the run is not reported converged: it restarts once on plain Pulay DIIS from a clean history, with a stderr warning (with inf, the level is 1e-2). A switch at or below the commutator floor (about 1e-6) can trigger the restart on a run that had converged, costing a few iterations.
smearing_sigmafloatnoneHartreeFermi–Dirac smearing width. Absent means integer occupations.
guessstring"minao"minao sad hcore (case-insensitive)"sad" is an alias of "minao" (the MINAO projection guess); the free-atom-SCF SAD guess is not selectable from config. Any other value is an error.
guess_basisstringunsetbundled basis nameSecondary-basis (Q-Chem BASIS2) guess: the same SCF (method, functional, charge, multiplicity and [scf] settings) is converged in this basis first and projected into the target basis as the starting point. The one exception: the secondary SCF never runs the stability check or the stability descent (check_stability, stability_descent); those apply to the target solution only. An unknown basis is an error at load time, and so is a periodic [cell] system. symmetry = true refuses it. With it set, guess selects only the secondary SCF's starting point. A secondary SCF that does not converge (iteration cap, stall or divergence) does not stop the run: the target starts from its default guess, with a warning. It also runs inside the df_guess pre-stage, and again at every geometry of an optimization or finite-difference Hessian. The rimp2 and pdep-rpa optimizers (task = "optimize") build their own RHF reference without it, so that combination is an error at load time. The guess can change which SCF state is reached. See SCF.
guess_projectionstring"fock"fock overlap (case-insensitive)How guess_basis is projected: fock (Q-Chem FOPPROJECTION, project the density and diagonalise the target Fock) or overlap (OVPROJECTION, project and re-orthonormalise the occupied MOs). Any other value, or setting it without guess_basis, is an error.
soscfboolfalseEnables the second-order (Newton) step in the SCF tail.
auroraboolfalseOpt-in AURORA auxiliary-curvature accelerator. Closed-shell RHF only (kind = "rhf", multiplicity 1). Same fixed point as DIIS; a speed-up (fewer J/K builds), not a convergence rescue.
trah_triggerfloatunset> 0Opt-in trust-region augmented-Hessian solver, engaged once the DIIS error falls below this value (rhf and uhf). A convergence rescue: fewer iterations, more wall time.
integral_threshfloat1e-12Integral screening threshold.
eri_precisionfloat1e-200 to 1e-8libint primitive-screening precision for the SCF J/K integrals. Omitted: FERRIC_ERI_PRECISION if set, else 1e-20. 1e-14 costs up to 6e-9 Ha in E_J for atoms past Ne; 0 disables primitive screening (1.8–5.7× slower per J/K build).
jk_storagestring"auto"auto memory disk direct splitWhere the raw RI-J/K three-index tensor lives. auto keeps it in memory when n_aux × n_bf² × 8 bytes fit [memory] budget_gb and the shared memory pool, keeps the packed symmetric half (n_aux × n_bf(n_bf+1)/2 × 8 bytes plus 64 unpacked aux rows of scratch) when only that fits, and otherwise, on one rank with the Coulomb operator and orbital/aux angular momenta up to 4/6, builds J by Split-RI-J (as split, below; a notice says so). For a tensor still needed beyond that (DF-K's, or J's on several ranks or for another operator) it measures this machine (an untimed warm-up block, four timed integral blocks, and a cold write and read of up to 64 MiB in the spill directory) and picks the cheaper of disk and direct for a 30-pass SCF. When J and K share one auxiliary basis, the raw tensor that DF-K needs is built as before and, when it is not held in memory, freed after DF-K's copy is made, with J from Split-RI-J. A spill directory without room for the packed tensor plus headroom rules disk out. memory is an error when even the packed half does not fit. disk always spills to $TMPDIR. direct recomputes each block per pass and writes nothing. Omitted: FERRIC_JK_STORAGE if set; an invalid value in either place is an error. J from the packed, spilled and recomputed tensors is bit-identical (all three are consumed through the same packed block stream); the unpacked in-core J differs from them in the last bits (summation order, about 1e-12 Ha in the energy). This holds for J only: K and the other consumers of the tensor (the DF-K dressing follows the tensor's own block order, and the packed tier's aux block size differs from the spill file's) can differ between tiers at the level of summation order. The tier chosen by auto depends on the budget (default 0.8 × available RAM) and on the free bytes in the shared memory pool, so the same input can pick a different tier on a busier machine or with a different budget; set memory, disk, direct or split to pin it. split stores no RI-J tensor on one rank (with J and K on one auxiliary basis too: the raw tensor DF-K needs is freed once DF-K's copy is made); on several ranks it warns and stores the RI-J tensor as auto would, with that tensor's memory. Where it applies, J comes from Split-RI-J (the density, and the fitted coefficients, contracted with the three-centre integrals' Hermite intermediates as they are computed, so no (P|μν) block is formed), recomputed every build on one rank; it is RI-J to 1e-14 relative per term (terms with a large Boys argument are evaluated as point-multipole interactions, with a proven bound on that error), uses tens of MB instead of the tensor, and costs more time than a tensor held in memory: on danuglipron PBE/def2-SVP the whole SCF took 81 s against 46 s with the tensor in memory, and 584 s with direct and 600 s with disk (all four energies identical to 1e-10 Ha; peak memory 2.5, 6.4, 3.3 and 6.5 GB). With split any tensor K still needs is stored as under auto, and the raw tensor shared with DF-K is freed once DF-K's copy is made. Applies to the RI-J tensor, and to the RI-K tensor when both use the same auxiliary basis on one rank; the RI-K dressed tensor follows [memory] budget_gb.
screeningstring"schwarz"schwarz csb csamcsb is rigorous and never looser than schwarz. csam is not a bound. It is refused for erfc (short-range) operators. See SCF: screening.
k_builderstring"direct"direct link cosxExchange builder. cosx with RI-J active (named, or the Kohn-Sham default) is RIJCOSX: J from RI-J, K from COSX, and the RI-K default is not applied; cosx next to an explicitly named df_k_aux is an error. link is ignored with a warning when DF-J/DF-K is active. Both are ignored with a warning for functionals with no exact exchange and for range-separated functionals. See SCF: choosing how exchange is built.
cosx_gridinline table{ radial = 35, angular = 194, prune = "sgx" }angular ∈ 6/14/26/50/110/194/302/434/590; prune ∈ none sgx nwchemThe COSX SCF grid. A table without prune is flat; with prune = "sgx", angular is the peak order of the pruned rows (50/110/194/302/434/590). Only with k_builder = "cosx"; otherwise it is an error. The inner table is strict.
cosx_final_passbooltrueRe-evaluate exchange once on a larger grid at the converged density and report that energy (the SCF-grid energy is printed and logged too). Without cosx_final_grid the grid is { radial = 50, angular = 302, prune = "sgx" }. Gradient tasks run without it; ROHF/ROKS skips it with a note (an explicit true there is an error). Only with cosx.
cosx_final_gridinline tablenoneas cosx_gridThe final-pass grid; setting it turns the pass on (cosx_final_pass = false with it is an error). RHF/RKS and UHF/UKS only. Only with cosx.
cosx_overlap_fitbooltrueOnly with cosx; otherwise it is an error.
cosx_grid_scheduleboolfalseRun the first SCF iterations on a coarse pruned sgx (25,110) exchange grid and switch to cosx_grid once the largest density change falls below 1e-3. Convergence is accepted only on the production grid, DIIS restarts at the switch, and the final-grid pass is unchanged. RHF/RKS and UHF/UKS; ROHF/ROKS refuse it. Only with cosx; setting it otherwise is an error.
cosx_backendstring"md3c1e"md3c1e cosx-aOnly with cosx; otherwise it is an error. cosx-a is the slower cross-check kernel.
cosx_screen_threshfloat1e-7≥ 0Only with cosx and md3c1e. 0 disables the screen.
cosx_half_transformstring"sparse"sparse denseOnly with cosx.
cosx_fp64_multiplierfloat1e5 with [precision] mode = "mixed" and cosx-kern in mixed_kernels, else 0≥ 0Precision-router threshold: tau = cosx_fp64_multiplier × cosx_screen_thresh. A kept (shell pair, sub-batch) unit whose Hölder K bound is below tau is counted as f32-eligible; the exchange matrix is unchanged (every unit is still computed in f64), so the key only changes the route_* counters. Setting it requires k_builder = "cosx", [precision] mode = "mixed" and cosx-kern in mixed_kernels, and cosx-kern is not in the shipped kernel set, so in this build setting the key is always an error; a value above 0 also needs md3c1e and cosx_screen_thresh > 0. 1e5 is the multiplier recommended by Laqua, Kussmann and Ochsenfeld, J. Chem. Phys. 154, 214116 (2021).
df_j_auxstringnone; def2-universal-jkfit for ksdft, pdep-rpa, rs-mp2-rpa, gw, bse-tda, tdhf-static-polarizability, tda, tddftaux basis name, or ""RI-J. With neither key set, rhf/uhf/rohf use exact 4-index J/K. df_j_aux = "" selects exact J for the kinds that default to RI-J (the SCF J/K log line then reads RI-JK via with a blank name). Unlike Python's run_dft, the CLI does not accept "exact", "none" or "off": any non-empty value is looked up as a basis name, and an unknown one fails the SCF.
df_k_auxstringas df_j_auxaux basis name, or ""RI-K. Use a JK-fit set. "" selects exact K.
jkstringthe kind's own: four-centre, except rs-mp2-rpa (RI-JK)exact rijk rijcosxMP2-family kinds (rimp2, oo-rimp2, att-rimp2, mp2-v, scs-mp2, scs-mp2-2terfc, laplace-mp2, laplace-sos-mp2, rs-mp2-rpa) with task = "energy" only: how the reference SCF builds J/K. rijk = RI-J + RI-K on jk_aux; rijcosx = RI-J on jk_aux + COSX K (the cosx_* keys apply); exact = four-centre. Same rule as the Python scf_jk kwarg. Cannot be combined with df_j_aux/df_k_aux; a k_builder under rijk (including rs-mp2-rpa's RI-JK default), or other than cosx under rijcosx, is an error. oo-rimp2 accepts only exact (it rebuilds exact J/K at every orbital step; RI-JK in the OO Fock builds is not implemented), and refuses df_j_aux/df_k_aux for the same reason. A fitted mode refuses df_increments and an explicit df_guess = true, and skips the default DF-guess pre-stage. The run log's scf_jk / jk_aux / k_builder record the route the SCF takes.
jk_auxstringdef2-universal-jkfitbundled JK-fit basis nameThe fitting basis for jk = "rijk"/"rijcosx". It is not the [mp2] auxbasis (an MP2-fit set for K costs mHa); a name without "jkfit" draws a warning. Setting it without jk, or with jk = "exact", is an error.
level_shiftfloat0.0HartreeVirtual-block shift. Left at 0 with a meta-GGA functional, the library applies 0.5.
mom_after_iterinteger0Maximum-overlap occupation pinning after this many iterations. 0 = aufbau throughout.
verboseboolfalseOne line per SCF iteration. The CLI's --verbose/-v flag ORs into this.
df_guessboolonTwo-stage SCF: DF first, then exact. It applies only on the closed-shell, non-laddered path (rimp2 and the other correlated kinds). rhf/ksdft use the convergence ladder, which does not compose with it, and lib.rs prints a warning there whenever it is enabled, including by default. Mutually exclusive with an explicit df_increments = true.
df_guess_auxstringdef2-universal-jkfitaux basis nameIt is an error when df_guess is off.
df_incrementsboolfalseDF-corrected incremental Fock SCF. Same scope as df_guess, and warned and ignored on rhf/ksdft.
df_increments_auxstringdef2-universal-jkfitaux basis nameIt is an error when df_increments is off.
check_stabilityboolfalseDiagnostic only: warns if the solution is a saddle point and never fails the run. On an RHF/HF run it reports two verdicts: internal (the singlet channel: is this RHF solution an RHF minimum?) and external RHF→UHF (the triplet channel: does breaking spin symmetry lower the energy?). A stretched geometry is often internally stable and externally a saddle: water / 6-31G at r(OH) = 2.0 Å gives +1.97e-2 and −3.07e-1. For an external instability, run kind = "uhf"; re-converging RHF does not help. RKS gets the internal verdict only (no triplet XC kernel, printed as a skip); UHF/UKS get the internal verdict, which already spans the independent α/β rotations. ROHF/ROKS, range-separated and meta-GGA are skipped entirely with a printed reason.
stability_descentboolfalseState selection: at a saddle of the orbital Hessian (UHF, or the RHF singlet channel), follow the downhill eigenvector and re-converge, keeping the lowest state. Turns check_stability on as well. Same as Python run_uhf(stability_descent=True). Only kind = "rhf", "uhf" or "ksdft" with task = "energy"; any other kind or task is an error. A KS functional with no stability verdict (range-separated, meta-GGA) skips the descent with a printed reason. Costs one Davidson per converged solve plus one SCF per descent. See examples/o2-uhf-stability-descent.toml.
symmetryboolfalsePoint-group-blocked closed-shell RHF or RKS: detect the Abelian subgroup, symmetrize and reorient the geometry, and diagonalize the Fock matrix per irrep. Only kind = "rhf" (Hartree-Fock, or RKS with [dft] functional) or kind = "ksdft", task = "energy", multiplicity 1, no [dft] dispersion, no [cell]/[qmmm]; anything else is an error (the result lives in a rotated frame, so gradients, optimizers and post-HF would be wrong; UHF/ROHF/UKS/ROKS blocking is not wired to the CLI). Under RKS the KS grid is built in the symmetry frame, where it is exactly symmetric, so no Fock or density symmetrisation is applied; a partially filled degenerate shell (stretched H2, singlet O2) is refused unless irrep_nelec chooses the occupation, because the grid can split such a shell. The energy is that of the SYMMETRIZED geometry; the printout and the run log give the point group, the per-irrep electron counts and the symmetrization shift (at most 1e-4 Bohr). No vector quantity is produced. The blocked SCF is one plain solve: [[scf.ladder]], df_guess, df_increments, soscf, aurora, trah_trigger, check_stability/stability_descent, smearing_sigma, mom_after_iter and k_builder = "cosx" are refused, and the default convergence-ladder escalation is not applied. Python: run_rhf(symmetry=True), run_dft/run_ksdft(symmetry=True) (return SymmetricRhfResult).
irrep_nelectableunseteven countsElectrons per irrep, irrep_nelec = { A1 = 4, B2 = 2 }: pins the occupation instead of aufbau across irreps (unlisted irreps follow aufbau). Requires symmetry = true. Python: run_rhf(irrep_nelec={...}), run_dft(irrep_nelec={...}).
ladderarray of tablesbuilt-in laddersee below[[scf.ladder]] rungs. Read only by rhf and ksdft.

[[scf.ladder]] rungs

Each rung overrides the flat [scf] settings. The rungs are walked in order, and the ladder stops at the first converged rung. Unknown keys are an error, as elsewhere.

KeyTypeDefaultAllowed valuesNotes
guessstring"minao"minao sad hcoreAs [scf] guess: "sad" is an alias of "minao", and any other value (including sad-smallbasis) is an error.
level_shiftfloatinherits
max_iterintegerinherits
df_j_aux, df_k_auxstringinherits
stall_windowintegernone
divergence_tolfloatnone
restartboolfalsetrue discards the incoming density.

[dft]

KeyTypeDefaultAllowed valuesNotes
functionalstring"LDA" (for ksdft)an XC name (LDA, PBE, B3LYP, wB97X-V, SCAN, r2SCAN, …) or a libxc nameRead by ksdft. wb97x-l-v ignores it with a warning. RPA/GW use [rpa] xc and TDDFT uses [tddft] xc instead.
grid_prunestring"nwchem-radial"none off flat; nwchem nwchem-like nwchem_like; nwchem-radial nwchem_radial; defgrid1 defgrid2 defgrid3Prunes the main grid only; the default is nwchem-radial, and none is the flat grid (with grid_weight_cut = 0, the previous default exactly). Absent, it is the default scheme at whatever grid_radial / grid_angular set (flat at grid_angular = 50, which has no region table). nwchem: NWChem-style angular pruning by radial region, about 23% fewer points at 75×110 with energies within 1e-9 Ha of the flat grid. nwchem-radial: the same angular pruning plus fewer radial shells for light atoms (H and He 50/75 of grid_radial, Li–Ne 66/75, heavier atoms all of it), 42-47% fewer points on organic molecules; live PBE/def2-SVP SCFs moved by 2e-7 to 4e-6 Ha against the flat 75×110 grid, whose own error against a 99×590 grid is 4e-4 to 3e-3 Ha on C10-C32 alkanes. defgrid1/defgrid2/defgrid3: ORCA-style grids (ORCA 6.1 DEFGRID1-3: ORCA's radial count per element row, its five-region angular tables, H and He one angular level lower) that fix their own radial and angular sizes, so grid_radial and grid_angular are an error with them; defgrid2 is as fast as nwchem-radial and much coarser (PBE/def2-SVP: -1.2e-3 Ha on n-C20H42 and +4.2e-3 Ha on danuglipron against the flat 75×110 grid, the same range as ORCA's own DefGrid2 results). Optimize and frequency runs differentiate the pruned energy. Setting it on a meta-GGA tda/tddft run replaces that run's 150 × 434 default (see grid_radial).
grid_radialinteger75 (meta-GGA TDDFT: 150)> 0Radial points per atom on the main grid. Only on a run with a Kohn–Sham grid, and not with kind = "gw". Optimize and frequency runs differentiate this grid. Same as Python grid_radial=. On tda/tddft with a meta-GGA [tddft] xc (SCAN, r2SCAN, TPSS) and no main-grid key set, the default is 150 × 434 instead (see Meta-GGA response needs a finer grid); setting any of grid_radial, grid_angular, grid_prune, grid_weight_cut replaces it.
grid_angularinteger110 (meta-GGA TDDFT: 434)6 14 26 50 110 194 302 434 590Lebedev order on the main grid. Same scope as grid_radial. An unsupported order is an error. Same as Python grid_angular=. On tda/tddft with a meta-GGA [tddft] xc (SCAN, r2SCAN, TPSS) and no main-grid key set, the default is 150 × 434 instead (see Meta-GGA response needs a finer grid); setting any of grid_radial, grid_angular, grid_prune, grid_weight_cut replaces it.
grid_weight_cutfloat1e-14finite, >= 0 (0 = no cutoff); e.g. 1e-14ORCA's WeightCut: drops every main-grid point whose final weight (radial × angular × Becke partition) is below this in absolute value. 0 turns the cutoff off (the grid is then bit-identical to the uncut one). At 1e-14, PBE/def2-SVP energies moved by under 1e-9 Ha and the XC time fell 12-15% (n-C20H42, danuglipron). A negative, NaN or infinite value is an error. Only on a run with a Kohn–Sham grid, and not with kind = "gw". Optimize and frequency runs differentiate the cut energy (the gradient drops the same points); it is discontinuous where a weight crosses the cutoff under displacement, by about cutoff × integrand. The VV10/NLC grid is not cut. Same as Python grid_weight_cut=. Setting it on a meta-GGA tda/tddft run replaces that run's 150 × 434 default (see grid_radial).
dispersionstringabsentd3bj, d3(bj), d3bj(<functional>), mbd, mbd(<functional>) (case-insensitive)Only on a Kohn–Sham SCF (kind = "ksdft", or rhf/uhf/rohf with functional). task = "optimize" and task = "frequencies" run on RKS, UKS and ROKS references; frequencies uses finite differences of the KS + dispersion gradient (also under [frequencies] hessian = "auto"), so hessian = "analytic" and "analytic-moving-grid" are errors. There is no "off" value; omit the key instead. d3bj with a functional that has no D3(BJ) row in ferric's table (listed by ferric_d3::d3bj_tabulated_functionals(), the simple-dftd3 BJ table; M06-2X has only a zero-damping fit and is refused), or mbd with a functional that has no MBD@rsSCS β, is an error.
lambdafloat0.6Only for wb97x-l-v.
omegafloat0.1Bohr⁻¹Only for wb97x-l-v. Note the unit differs from [mp2] omega.

[mp2]

This section is shared by the whole MP2 family, ccsd, ccd, ccsd(t), linlccd, drpa, the double hybrids and tda/tddft, all of which read auxbasis and frozen_core from here. drpa, linlccd and a local rimp2 read nothing else from it (plus linlccd_variant for linlccd); any other [mp2] key on them is an error.

KeyTypeDefaultAllowed valuesNotes
auxbasisstring"cc-pvdz-ri"; "cc-pvdz-rifit" for tda/tddftaux basis nameThe two defaults name the same bundled set (cc-pvdz-rifit is an alias of cc-pvdz-ri).
frozen_coreint, string or bool0integer ≥ 0, "auto", "none", true (= auto), false (= 0)"auto" gives the standard small core for this molecule after the ECP is applied, and the run prints the resolved count.
omegafloat0.420Å⁻¹att-rimp2 (erfc), rs-mp2-rpa. Ignored with a warning when attenuator = "terf". An error on att-rimp2 with att_operator = "terfc".
kappafloatnoneκ > 0, Hartree⁻¹κ-regularized MP2 for the exact rimp2; an error with [local]. Absent = plain MP2.
c_osfloat1.2 (scs-mp2), 1.27 (scs-mp2-2terfc), 1.3 (laplace-sos-mp2)
c_ssfloat1/3 (scs-mp2), 4.05 (scs-mp2-2terfc)laplace-sos-mp2 warns and ignores it.
n_quadinteger73 5 7laplace-mp2, laplace-sos-mp2. Any other value is an error.
sos_formulationstring"mo"mo ao ao-sparselaplace-sos-mp2. mo and ao are exact and agree to round-off. ao-sparse is approximate and requires domain_cutoff_bohr.
domain_cutoff_bohrfloatnone> 0, BohrRequired by ao-sparse. An error with the other formulations.
formulationstring"delta-lr"delta-lr coupled-ringsrs-mp2-rpa.
attenuatorstring"erf"erf terfrs-mp2-rpa. terf needs FERRIC_TERF_TABLE_DIR. An error on att-rimp2 (use att_operator).
r0float1.6828 (= 3.18 Bohr)Års-mp2-rpa with terf only. An error on att-rimp2 (use att_r0).
terf_omegafloatlinked, ω = 1/(r0√2)Å⁻¹, > 0rs-mp2-rpa with attenuator = "terf" only (an error elsewhere). Sets the terf/terfc sharpness independently of r0. Same as Python run_rs_mp2_rpa(terf_omega=).
att_operatorstring"erfc"erfc terfc (case-insensitive)att-rimp2 only (an error on any other kind). The short-range operator on the MP2 correlation; the SCF stays Coulomb. terfc is the Python run_terfc_rimp2 and needs FERRIC_TERF_TABLE_DIR.
att_r0float1.05Å, > 0att-rimp2 with att_operator = "terfc" only; an error with erfc.
att_omegafloatlinked, ω = 1/(r0√2)Å⁻¹, > 0att-rimp2 with att_operator = "terfc" only; an error with erfc (use omega) and on any other kind. Sets the terfc seam sharpness independently of att_r0, via Operator::terfc_with_omega; r0·ω = att_r0 × att_omega. Needs FERRIC_TERF_TABLE_DIR. With [local] integral-direct it takes the same schwarz_skip cut as the other operators.
r0_sweeparray of floatsnoneÅ, > 0rs-mp2-rpa with terf only. Reuses one SCF for several r0 values. r0 is then ignored with a warning.
r0_bondedfloat0.75Åscs-mp2-2terfc.
r0_nonbondedfloat1.05Å, > r0_bondedscs-mp2-2terfc.
linlccd_variantstring"hh"hh drivers-only fulllinlccd only (an error on any other kind), exact and local alike: it selects the method. drivers-only equals RI-MP2; full adds the pp ladder (CCD-like VVVV memory). Any other value is an error.
mp2v_r0float1.00Å, > 0mp2-v. Also sets the VV10 damping r0. It is correlated with mp2v_b in the published fit.
mp2v_bfloat11.0mp2-v.
mp2v_cfloat0.0089mp2-v. Fixed in the paper. Changing it leaves the published parameterization.
mp2v_attenuatorstring"terfc"terfc erfcmp2-v. terfc needs FERRIC_TERF_TABLE_DIR. erfc is an unparameterized control.
mp2v_omegafloatlinked, ω = 1/(r0√2)Å⁻¹, > 0mp2-v with terfc only. Setting it leaves the fitted parameterization.
mp2v_vv10_dampingstring"terfc"terfc nonemp2-v. none double-counts short-range correlation.
mp2v_nlc_n_radialinteger50> 0mp2-v VV10 grid.
mp2v_nlc_n_angularinteger50> 0mp2-v VV10 grid (unpruned).
oo_max_iterinteger100≥ 1oo-rimp2 only (closed and open shell). Orbital-optimization iterations.
oo_grad_convfloat1e-4> 0oo-rimp2 only. Convergence threshold on the orbital-gradient norm.
oo_level_shiftfloat0.1Hartree, ≥ 0oo-rimp2 only. Level shift on the approximate diagonal orbital Hessian.
oo_diis_sizeinteger6≥ 1oo-rimp2 only. DIIS subspace for the orbital rotations. The four oo_* keys match Python run_oo_rimp2(oo_max_iter=, grad_conv=, level_shift=, diis_size=); on any other kind they are an error.

[cc]

Closed-shell CCSD knobs, read by ccsd and ccsd(t) only; on any other kind the section is an error. The aux basis and frozen core stay in [mp2].

KeyTypeDefaultAllowed valuesNotes
ladderstring"auto"auto dense ri (lower case only)How the particle–particle ladder is evaluated. dense holds the nv⁴ (ab|cd) block; ri rebuilds it from the RI 3-index tensors every iteration, holding at most one nv⁴-sized panel at full width (dense holds four) and narrower panels when the budget requires (same energy to round-off, less memory, slower per iteration); auto takes dense whenever its memory plan fits the budget and ri otherwise, and says so on stderr when it falls back. The run prints the route taken (ladder = dense (auto)). Python run_ccsd(ladder=), run_ccsd_t(ladder=). See Coupled cluster.

[local]

The local approximation of a correlated method. method.kind names the method (rimp2, drpa, linlccd or, integral-direct only, att-rimp2); this section says whether and how its amplitudes are truncated. Without it (or with scheme = "none") the method is computed exactly. On any other kind the section is an error. The local runs are closed shell and task = "energy" only, and every printout and run-log result carries the model: "<method> (exact)" with "local": null, or the scheme, eps and kept fraction. See The MP2 family and Exact and local correlation.

KeyTypeDefaultAllowed valuesNotes
schemestring"none"none amplitude-thresholdamplitude-threshold: drop pair amplitudes whose localized integral is at or below eps (single threshold). Any other value is an error.
epsfloatrequired with amplitude-threshold≥ 0, finiteThe threshold is part of the model and has no default. 0 keeps every amplitude and reproduces the exact method. An error with scheme = "none".
eps_sweeparray of floatsnoneeach ≥ 0drpa only. Several ε on one SCF and one localized assembly; sorted and de-duplicated, one result block per point. Instead of eps, not with it.
referenceboolfalseAlso compute the exact method (canonical RI-MP2, canonical plasmon dRPA, exact LinLCCD) and print the local error against it. Costs the full exact calculation. An error with scheme = "none".
integral_directboolfalserimp2 only. The integral-direct local MP2: never forms the global 3-index tensor.
aux_radiusfloat10.0Bohr, > 0Integral-direct only (an error otherwise). Aux fit-domain radius.
virt_radiusfloat12.0Bohr, > 0Integral-direct only. Virtual domain radius.
ao_tailfloat1e-3≥ 0Integral-direct only. 0.0 keeps every shell.
schwarz_skipfloat1e-5≥ 0Integral-direct only. Cauchy-Schwarz cut on the batch 3-index triples: a triple is skipped when sqrt(max (P|P)) * sqrt(max (ab|ab)) < schwarz_skip, both factors from the run's own operator (Coulomb, erfc or terfc; the terfc factors carry a 1e-5 relative table-error inflation each, so the product is scaled by (1 + 1e-5)²). For terfc it is available for orbital shells up to g, r0 in [0.3, 2.5] Å and r0·ω ≤ 8.9, and errors outside that (set 0.0 to run anyway). See integral-direct local MP2. 0.0 evaluates every triple (bit-identical to a run without the cut).
batch_mergeinteger4≥ 1Integral-direct only.
gate_calfloatnone (gate off)> 0Integral-direct only. Pair-gate calibration (~0.7 Coulomb, ~0.02 erfc ω = 1).
virt_schwarz_kappafloatnone (off)> 0Integral-direct only. ε-linked Schwarz virtual-candidate screen.

[rpa]

Read by pdep-rpa, gw, bse-tda, tdhf-static-polarizability, and, for trunc_thresh only, rs-mp2-rpa.

KeyTypeDefaultAllowed valuesNotes
auxbasisstring"cc-pvdz-ri"aux basis name
frozen_coreint, string or bool0as [mp2]
xcstringnone (HF reference)XC nameSwitches the reference to RKS, or to UKS when open shell. Required by tdhf-static-polarizability. bse-tda ignores it. pdep-rpa with task = "optimize" uses an RHF reference; xc applies only to task = "energy".
n_quadinteger20 (energy runs); 16 (task = "optimize")Python run_pdep_rpa also uses 20.
quadraturestring"gauss-legendre" (CLI, Python and the Rust library QuadratureConfig::default())gauss-legendre gauss_legendre gl; minimax mini-max mm; chebyshev-tan chebyshev_tan chebyshev ct
u0float0.5Warn-and-ignore under minimax, which derives its own u₀.
trunc_threshfloat1e-4; 0.0 (full rank) for rs-mp2-rpaPDEP truncation.
eigensolver_conv_threshfloat1e-6 (energy); 1e-8 (optimize)Alias: davidson_conv_thresh.
chi0_sparsitystring"dense"dense, boys, boys:<thresh>, auto, auto:<cutoff>, auto:<cutoff>:<thresh>, each boys/auto form optionally suffixed @<radius_bohr>
run_diagnosticsboolfalse
export_eigpot_prefixstringnoneWrites <prefix>_eigpot_NNN.cube.
export_eigpot_countinteger10Capped at the number of eigenpotentials.
cube_spacingfloat0.2Bohr
cube_marginfloat4.0Bohr
export_npzstringnonepathTurns on the NPZ property bundle. The compute_* keys below default to true only when this is set.
compute_espbooltrueESP at the nuclei.
compute_esp_surfaceboolfalseESP on a vdW shell.
esp_surface_vdw_scalefloat1.4
esp_surface_n_angularinteger110Lebedev order
compute_polarizabilitybooltruealpha_tensor, the molecular static α.
compute_alpha_atomicbooltruealpha_atomic: the Krishtal–Senet–Van Alsenoy intrinsic per-atom α (JCP 125, 034312 (2006)), always Becke-partitioned; c6_partition does not affect it. Charge transfer between atoms is excluded; with compute_polarizability also on, the remainder alpha_ct = alpha_tensor − Σ_A alpha_atomic is exported.
compute_electric_fieldbooltrue
compute_density_matrixbooltrue
compute_dipolebooltrue
compute_hirshfeld_chargesbooltrueProatoms are free-atom SCF densities in the molecule's basis and SCF settings, as in Python's hirshfeld_charges.
compute_lowdin_chargesbooltrue
compute_mulliken_chargesbooltrue
compute_chelpg_chargesbooltrue
compute_resp_chargesbooltrue
compute_c6booltrueWith c6_source = "pdep", also exports alpha_ct_dynamic (nfreq, 3, 3): molecular α(iω) − Σ_A α^A(iω).
allow_partial_npzboolfalseBy default a bundle missing a requested property fails the run.
c6_sourcestring"ts"ts pdep mbd
c6_partitionstringhirshfeld for pdep, becke for ts/mbdbecke hirshfeld

[gw]

Read by gw. bse-tda and tdhf-static-polarizability read frozen_core, and tdhf-static-polarizability also reads scissor. The [rpa] section supplies the screened interaction.

KeyTypeDefaultAllowed valuesNotes
methodstring"g0w0"g0w0 cohsex evgw0 evgw (case-insensitive)
qp_mos[lo, hi]HOMO−2 … LUMO+2absolute MO indices, half-open
max_ev_iterinteger20evGW/evGW0 outer loop.
ev_conv_threshfloat1e-4Hartree
pade_nptsinteger0 (= [rpa] n_quad)
qp_newton_dampfloat1.0
frozen_coreint, string or boolfalls back to [rpa] frozen_coreas [mp2]Also overrides the PDEP frozen core, so W and Σ agree.
scissorfloat0.0Hartreetdhf-static-polarizability only. At 0.0 some molecules hit a negative α diagonal and the run errors. The remedy is about 0.3–0.4 Ha.
referencestring"uhf"uhf rohf (case-insensitive)gw on an open-shell molecule only: the reference is UHF or ROHF (UKS or ROKS with [rpa] xc). An error on a closed-shell molecule or another kind. Same as Python run_u_gw(reference=).

[tddft]

Read by tda and tddft. Also set [mp2] auxbasis (see above).

KeyTypeDefaultAllowed valuesNotes
n_rootsinteger3
xcstringnone (HF reference: CIS or TDHF)XC nameSelects the reference functional and the f_xc kernel. Meta-GGA, VV10 and range-separated functionals are refused.
c_hffloatthe functional's short-range exact-exchange fraction; 1.0 with no xc

[optimize]

Read when task = "optimize".

KeyTypeDefaultAllowed valuesNotes
max_stepsinteger100
g_max_threshfloat4.5e-4Hartree/Bohr
g_rms_threshfloat3.0e-4Hartree/Bohr
e_convfloat1e-6Hartree
trust_radiusfloat0.1Initial step size.
coordinatesstring"cartesian"cartesian cart; internal internals redundant-internal redundant_internal

[frequencies]

Read when task = "frequencies".

KeyTypeDefaultAllowed valuesNotes
hessianstring"auto"auto; analytic; analytic-moving-grid; fd finite-differenceauto uses the analytic Hessian for RHF (closed shell), UHF (aufbau occupations) and ROHF (kind = "rohf" without [dft] functional) with the Coulomb operator and exact four-centre J/K (RHF, UHF and ROHF also with density-fitted J and/or K: [scf] df_j_aux and/or df_k_aux set, ω = 0 only, no external potential (an ECP is analytic with RI), a fitting-basis metric with no eigenvalue below 1e-10 (RI-K: none below 1e-7 and cond(V) at most 1e8 for auto), and RI-K memory that fits the [memory] budget (it grows like N⁴: about 10 GB for 30 atoms with 300 basis functions, the same for the two spin channels of UHF and ROHF); no range-separated RI-K, no COSX), a basis up to f functions and a libint2 with second derivatives. For RHF and UHF, ECPs (on the default quadrature backend; FERRIC_ECP_BACKEND=libecpint is not analytic), external point charges, Gaussian-smeared external charges and a uniform external field are analytic; for ROHF an external potential is not (an ECP is). Implicit solvent, polarizable embedding, fractional occupations, MOM and constraints are not analytic for any reference. For closed-shell Kohn-Sham (kind = "ksdft", or rhf with [dft] functional) auto uses the analytic-moving-grid Hessian wherever it applies (below); UKS, ROKS, every KS case it refuses and systems whose response AO table (basis functions × grid points × 4 for a GGA, 8 bytes each) exceeds 2 GiB (on the default 75×110 grid, a GGA above about 8,100 basis functions × atoms) use finite differences, decided before the SCF; an error it raises after the SCF (a non-converged SCF included) is returned, as for the Hartree-Fock routes. Anything else uses finite differences. analytic is the Hartree-Fock analytic Hessian and an error where it does not apply (every Kohn-Sham reference included). analytic-moving-grid is the analytic closed-shell Kohn-Sham Hessian on the production (moving) grid, Becke weight response included, so it is the Hessian of the surface the KS gradient differentiates and agrees with fd to the finite-difference error. It applies to kind = "ksdft" (or rhf with [dft] functional) on a closed shell, LDA or GGA functionals, pure or global hybrid, with exact or density-fitted J and K. It is an error, before the SCF, for Hartree-Fock, open-shell (UKS/ROKS) references, meta-GGA, VV10, range-separated hybrids, ECPs, external potentials, [dft] dispersion, and what the analytic RHF Hessian refuses (COSX, implicit solvent, fractional occupations, MOM). The output prints Hessian = analytic, analytic-moving-grid or finite-difference; the JSON run log carries a frequencies record with the full-precision wavenumbers and Cartesian Hessian.
deltafloat5e-3Bohr, finite and > 0Central-difference step, finite-difference Hessians only. Check the printed Hessian asymmetry: it is zero in exact arithmetic.

[memory]

KeyTypeDefaultAllowed valuesNotes
budget_gbfloatautofinite and > 0Precedence: this key, then FERRIC_MEM_BUDGET_GB, then the legacy FERRIC_OOC_BUDGET_GB/FERRIC_ERI3_BUDGET_GB, then 0.8 × available RAM, then 2 GiB. A value of 0, a negative value or NaN is an error; omit the key for auto. It bounds the ledgered allocations, not total process memory.
three_index_budget_gbfloat—Deprecated alias. budget_gb wins if both are set.

[gpu]

Optional CUDA backend. A default build has no GPU code: there mode = "on" is an error and mode = "auto" prints a notice and runs on the CPU. A GPU run is deterministic run to run on one device but is not bit-identical to the CPU run (a different summation order, like FERRIC_BLAS_THREADS above 1).

KeyTypeDefaultConstraintMeaning
presetstring"off"off, auto, on, mixed, auto-mixedOne word that sets mode and precision together; see the presets table below. The root-level key gpu = "mixed" is the same as [gpu] preset = "mixed" (a file cannot hold both a root gpu string and a [gpu] table). A mode, precision or mixed_kernels key, or its env var, that disagrees with the preset is an error naming both; one that agrees is kept. device, memory_gb and min_flops combine with any preset. Env: FERRIC_GPU_PRESET.
modestring"off"off, auto, onauto uses a device when one is usable and otherwise prints a notice and runs on the CPU; on makes an unusable device an error. The Kohn-Sham XC grid uses the device only under on: under auto it stays on the CPU, because it was measured slower on the device (GTX 1080; see installation). Env: FERRIC_GPU.
deviceinteger0a CUDA ordinalWhich device to use. Env: FERRIC_GPU_DEVICE.
memory_gbfloat0.8 x freefinite and > 0Device-memory pool (decimal GB). A GEMM that does not fit runs on the CPU. Env: FERRIC_GPU_MEM_GB.
min_flopsinteger549755813888Smallest 2*m*n*k that an f64 einsum! matrix product sends to the device; the default is the rounded-up value of a crossover rule applied to products up to 6144³: the device does not clearly beat 6 CPU cores on a single f64 product at any shape measured (at best a 2-8% median win at 6144³ in two of three runs, a loss in the contested run, and at 4096³ and below the CPU won). Products of 2^39 FLOP or more go to the device, and that region is unmeasured. The device RI-MP2 energy does not consult it. Env: FERRIC_GPU_MIN_FLOPS.
precisionstring"f64"f64, mixedDeprecated alias of [precision] mode. It still works; given together with [precision] mode, the two must agree (a different value is an error). The run prints a deprecation note. Env: FERRIC_GPU_PRECISION.
mixed_kernelsstring arraythe kernels this build shipsas [precision] mixed_kernelsDeprecated alias of [precision] mixed_kernels. It still works; given together with [precision] mixed_kernels, the two must agree (a different list is an error). Env: FERRIC_GPU_MIXED_KERNELS (comma-separated).
gpu = "mixed"   # same as [gpu] preset = "mixed": device on, mixed precision for every shipped kernel

GPU presets

A preset fills in every knob below that is not given; the filled-in knobs print with [source: preset] in the audit lines.

Presetmodeprecisionmixed_kernels
offofff64the build default
autoautof64the build default
ononf64the build default
mixedonmixedevery kernel this build ships (rimp2-energy: the Coulomb, erfc and terfc RI-MP2 energy, closed-shell and unrestricted)
auto-mixedautomixedevery kernel this build ships (rimp2-energy: the Coulomb, erfc and terfc RI-MP2 energy, closed-shell and unrestricted)

preset = "off" is the same as no [gpu] key. A narrower mixed_kernels list is allowed under mixed and auto-mixed; naming a kernel the build does not ship is an error. Precedence for the preset itself: the command-line flag ferric --gpu <preset> input.toml (or --gpu=<preset>), then [gpu] preset or the root gpu key, then FERRIC_GPU_PRESET, then off. The flag labels its audit line [source: command line]; a mode, precision or mixed_kernels key or env var that disagrees with the flag is an error naming both, and --gpu given twice, without a value or with an unknown name is refused (exit code 2).

ferric --gpu mixed input.toml

[precision]

Which kernels may run in mixed precision, independent of the device. [gpu] chooses only device placement; its precision and mixed_kernels keys are deprecated aliases of the two keys here (either spelling works; both given with different values is an error). Every result records what ran in its precision provenance record (see Run logs). The default, no [precision] section, is f64 everywhere and bit-identical to a file without the section.

KeyTypeDefaultConstraintMeaning
modestring"f64"f64, mixedmixed runs the kernels in mixed_kernels with f32 storage and f32 panels accumulated in f64; every other contraction stays f64. A kernel that runs on a device needs [gpu] mode auto or on; with [gpu] mode = "off" (CPU-only mixed precision) only kernels with a CPU mixed path are accepted, and naming or defaulting to a device kernel is an error that lists the CPU kernels this build admits (dfj-f32, opt-in; cosx-kern is a CPU kernel but not shipped). A mixed result is not an f64 result: its error is bounded, measured and documented on the validation page, not validated against a reference code. Python: ferric.configure_precision(mode=...). Env: FERRIC_GPU_PRECISION.
mixed_kernelsstring arraythe kernels this build shipsrimp2-energy, ccsd-amplitudes, dfk-occ, cosx-kern, dfj-pack, xc-vxc, dfj-f32Which kernels may run in mixed precision; requires mode = "mixed". This build ships rimp2-energy: with mode = "mixed" the device RI-MP2 energy keeps B_ov as f32 and accumulates its panels in f64 (the default precision stays "f64", so shipping the kernel changes nothing until you ask). rimp2-energy covers the RI-MP2 energy under the Coulomb, erfc and terfc operators, closed-shell and unrestricted (open-shell, one f32 B_ov per spin); the erf operator, composite fitted operators, SR-MP2 in RS-MP2+RPA, OO-MP2 and kappa-regularised runs use the f64 device kernel. xc-vxc (the Kohn-Sham XC potential matrix of LDA and GGA functionals from f32 operands with f64 accumulation, used only in SCF iterations whose density is still changing; see the validation page) is opt-in: it is admitted when the list names it, but it is not in the default set, so mode = "mixed" or gpu = "mixed" alone leaves it out; naming it under any [gpu] mode other than "on" is an error, since the XC grid stays on the CPU under auto (measured slower on the device; see installation). dfk-occ (the device DF-K occupied path with the dressed 3-index tensor resident as f32 and f64 panel accumulation, used only in SCF iterations whose density is still changing) is opt-in on the same terms, named in the list and under mode = "on": its converged energies match the f64 device SCF to within the f64 run's own termination noise, but a mixed SCF saved only about 4% of the wall time on the one device measured, and was 3-6% slower with diffuse basis sets (see the validation page). dfj-pack (the device RI-J with the packed raw 3-index tensor resident as f32, f64 accumulation) is named in this build but not shipped. dfj-f32 (the CPU RI-J with its stored 3-index tensor kept as f32, f64 accumulation, half the memory) is opt-in: it runs when the list names it, under any [gpu] mode; every SCF RI-J build reads the rounded tensor, so the converged energy carries a first-order error of the µEh scale (PBE/def2-SVP: -0.9 µHa on n-C20H42, +5.4 µHa on danuglipron, whose peak memory fell from 8.0 to 6.1 GB and J time from 5.7 to 3.5 s) (there is no SCF ladder and no f64 exit), Hessian, response and gradient RI-J builds stay f64, and a gradient after an f32 SCF is taken at the f32 density. Naming a kernel this build does not ship yet is an error, and a build that ships no mixed kernel refuses mode = "mixed" ("no mixed-precision kernel is available in this build"). Kernels that are not names here (SCF diagonalisation, DIIS, metric inverses, GW, grid construction and Becke weights, the XC density and the functional, meta-GGA τ) never run below f64. One kernel name per list item: an empty list or a comma inside an item is an error. Python: ferric.configure_precision(mixed_kernels=[...]). Env: FERRIC_GPU_MIXED_KERNELS (comma-separated).
[gpu]
mode = "on"

[precision]
mode = "mixed"
mixed_kernels = ["rimp2-energy"]

[output]

KeyTypeDefaultAllowed valuesNotes
jsonstring or bool<input-stem>.ferric.jsonl beside the inputa path, true (the default path), false (off)On by default. See Run logs. --json <path> and --no-json override it.

[qmmm]

When present, the PQR supplies both the geometry and the MM charges. It cannot be combined with [external_potential]; doing so is an error.

KeyTypeDefaultAllowed valuesNotes
pqrstringrequiredpathGeometry in Å and charges in e. The element is read from the atom name.
qm_indicesinteger array[]zero-basedUse this, or qm_seeds + qm_radius_angstrom, but not both.
qm_seedsinteger array[]zero-based
qm_radius_angstromfloatnoneÅ, > 0Requires qm_seeds.
link_bondsarray of [qm, mm][]Required when the cut crosses a covalent bond.
boundary_schemestring"delete-host"keep delete-host rc rcdSetting a non-default value without link_bonds is an error.

See QM/MM. Smeared PQR charges, polarizable sites and MM force fields are Python only. (Smeared charges outside QM/MM are available through [external_potential] with width.)

[pcm]

IEF-PCM implicit solvent (ferric-pcm). Absent means vacuum. Honoured on task = "energy" by rhf, uhf, rohf, ksdft and pdep-rpa (for pdep-rpa the RPA correlation is evaluated on the solvated reference), and on task = "optimize" by rhf, uhf, rohf and ksdft (their gradients carry the analytic IEF-PCM term). Any other kind, any other task (frequencies: there is no PCM Hessian), [cosmo] in the same file, or pdep-rpa with [rpa] export_npz is an error. The solvent table and checks are shared with Python run_rhf(solvent=...). See examples/water-pcm.toml.

KeyTypeDefaultAllowed valuesNotes
epsilonfloat—finite and > 1Dielectric constant. Give exactly one of epsilon and solvent.
solventstring—water (78.4) dmso (46.7) methanol (32.6) ethanol (24.9) acetone (20.7) dichloromethane/dcm (8.93) thf (7.43) chloroform (4.71) toluene (2.38) hexane (1.88), case-insensitiveDielectric constants at 298 K. An unknown name is an error.
lebedev_orderinteger1106 14 26 50 110 302Tesserae per atomic sphere.

[cosmo]

Conductor-like implicit solvent, applied to every SCF variant. It cannot be combined with [pcm]. task = "energy" only: COSMO has no analytic gradient, so task = "optimize" and task = "frequencies" with [cosmo] are errors. Use [pcm] (which has a gradient) for optimization.

KeyTypeDefaultAllowed valuesNotes
epsilonfloatrequired when the section is presentfinite and > 1Default in code is 78.39, but serde has no default for this key.
radius_scalefloat1.17> 0Multiplies Bondi radii.
lebedev_orderinteger1106/14/26/50/110/302
s_matrix_kindstring"GaussianSmeared"GaussianSmeared PointChargeSerde variant names, case-sensitive.

[external_potential]

Strict, like every other section: an unknown key here or inside a point charge is an error.

KeyTypeDefaultAllowed valuesNotes
point_chargesarray of { q, x, y, z, width }[]q in e; x, y, z in Bohr; width in Bohr, finite and > 0Written as [[external_potential.point_charges]] tables. width is optional: with it the charge is Gaussian-smeared, density ∝ exp(−r²/width²) and potential q·erf(r/width)/r (the Python smeared_charges=); without it the charge is a point. See examples/water-rhf-smeared-charge.toml. The order of the list changes the one-electron potential only by summation roundoff (reordering 6458 charges: 3e-15 to 1e-14 relative) and ferric does not reorder it; a different SCF iteration count was seen only for the closed-shell direct-J/K path, on rc7 and, on main, with FERRIC_SCF_INCREMENTAL=1 below the incremental noise floor (see Sharp bits, "energy_conv is a sanity bound").
field[Ex, Ey, Ez]noneatomic unitsUniform electric field.

With both empty, the run is identical to a vacuum run.

[cell]

A 3-D periodic system. When present, the run goes to the periodic drivers instead of the molecular ones. The [molecule] XYZ supplies the atoms of the reference cell (in Å, like every XYZ). Energies are Hartree per cell.

method.kind must be rhf, uhf, rohf, ksdft, rimp2 (periodic MP2) or pdep-rpa (periodic dRPA). rhf/uhf/rohf with [dft] functional run RKS/UKS/ROKS. Any other kind is an error. At the Gamma point (no kmesh) RKS, UKS and ROKS also run range-separated (CAM) GGA hybrids with either jk: any libxc one is accepted (HSE06, or a libxc ID such as HYB_GGA_XC_CAM_B3LYP or HYB_GGA_XC_WB97X; the names CAM-B3LYP and wB97X do not resolve); HSE06 and CAM-B3LYP are pinned against PySCF, open-shell wB97X is accepted, not pinned (the attenuated exchange is the k = 0 point of the k-point range-separated exchange; PySCF Gamma pins agree to 1e-12; with jk = "rsgdf" the full-Coulomb fit is built twice, once for the Gamma J and once inside the attenuated exchange, so the build costs about 1.9× a plain Gamma run, and the exchange also contracts a J it discards every iteration; with jk = "dense" the tensors take 40 nao⁴ bytes, not 8, and max_eri_gb counts all of them); range_split with a range-separated hybrid is an error; task = "optimize" with HSE06 or CAM-B3LYP runs on the RKS, UKS and ROKS routes with either jk (the analytic forces of gamma_rsh::gamma_rks_rsh_gradient, gamma_rsh::gamma_uks_rsh_gradient and gamma_rsh::gamma_roks_rsh_gradient, the 1×1×1 k-point force on the SCF's own dense-AFT or RS-GDF exchange); other range-separated hybrids (wB97X) are energy only. The Gamma-point UKS and ROKS routes run the SCAN / r2SCAN energy with jk = "dense" (per-spin τ; jk = "rsgdf", task = "optimize" and any other meta-GGA are errors; kmesh = [1, 1, 1] is the Gamma point and runs the UKS SCAN / r2SCAN force; the k-point ROKS SCAN / r2SCAN force and stress are library-only, #498). task = "optimize" works at the Gamma point for the SCF routes (RHF, UHF, ROHF, RKS, UKS, ROKS) with either jk: it moves the atoms at a fixed lattice using the analytic periodic force, and prints the final gradient (Hartree/Bohr per cell). A Gamma-point RKS with SCAN / r2SCAN / TPSS optimizes too, with jk = "dense" only (the force is the 1×1×1 k-point force fed the Gamma SCF; it builds the 1×1×1 dense kernels, 32 nao⁴ bytes on top of the 8 nao⁴ Gamma ERI, and max_eri_gb counts all of them before the SCF); jk = "rsgdf" with a meta-GGA and task = "optimize" is an error. With kmesh it works for rhf, uhf and rohf (no functional) and for Kohn-Sham (ksdft, or rhf/uhf/rohf with [dft] functional: RKS, UKS, ROKS) using the analytic k-point force, with either jk on every route except the dense-only cases named below (SCAN / r2SCAN on the UKS route need jk = "dense"; unsplit RS-GDF: range_split with task = "optimize" is an error). The functionals are LDA, GGA and global hybrids such as PBE0 and B3LYP on every Kohn-Sham route, plus the pure meta-GGAs SCAN and r2SCAN on the closed-shell route (either jk) and the UKS route (jk = "dense"), TPSS on the closed-shell route (jk = "dense"), and the range-separated hybrids HSE06 and CAM-B3LYP (HYB_GGA_XC_CAM_B3LYP; other range-separated hybrids such as wB97X are energy only) on every Kohn-Sham route (RKS, UKS, ROKS; either jk); the force differentiates the energy's own XC grid (n_radial/n_angular/neighbour_cutoff) and, for a hybrid, its exxdiv. It is an error for range-separated hybrids other than HSE06 / CAM-B3LYP, VV10 functionals, TPSS on RS-GDF or an open-shell route, hybrid meta-GGA, meta-GGA forces on ROKS (its SCAN / r2SCAN energy runs, jk = "dense"; the library k-point ROKS SCAN / r2SCAN force and stress exist but are not wired, #498) and UKS meta-GGA forces and stress on RS-GDF, and for rimp2/pdep-rpa. Cells whose basis carries an ECP are supported by the k-point force and stress of every route above (RHF, UHF, ROHF, RKS, UKS, ROKS). The lattice is fixed in task = "optimize" (no stress relaxation). The analytic stress of a k-point SCF is printed by a task = "energy" run with stress = true (see the key below): rhf, uhf, rohf, ksdft and the [dft] functional routes (RKS, UKS, ROKS), either jk except where the stress key below says jk = "dense", the 3×3 tensor in Hartree/Bohr³ and in GPa, and the same two tensors in the run log's run_end record (extra.stress_hartree_per_bohr3, extra.stress_gpa, full precision; see run logs). It equals the Python with_stress=True tensor bit for bit (Python: run_rhf_kpts, run_uhf_kpts, run_rks_kpts, run_uks_kpts, run_roks_kpts, run_rohf_kpts), with the gate shared: every route runs with either jk, the closed-shell SCAN / r2SCAN stress and the range-separated hybrid (HSE06, CAM-B3LYP) stress of every Kohn-Sham route (RKS, UKS, ROKS) included, except the UKS SCAN / r2SCAN stress, which needs jk = "dense" (unsplit RS-GDF: range_split with stress = true is an error). The k-point force and stress use an explicit functional allowlist that is separate from the energy gate (ferric_pbc::kxcgrad::check_deriv_functional, per route and J/K, wrapped by check_kpoint_deriv_functional, which the Python bindings call too). The run_end record of an optimization carries the returned geometry and the gradient there at full precision (extra.final_geometry_bohr, extra.final_gradient_hartree_per_bohr). solve_krks (every k-point Kohn-Sham energy run, run_rks_kpts and the CLI included) clones S(k) and h(k) transiently during the SCF (2 N_k n² × 16 bytes, not charged to the budget); the periodic XC Bloch AO cache of the SCF does not follow [memory] budget_gb. Each optimizer step runs a fresh SCF from the core guess; at a k-point mesh with uhf it can land in a different spin solution between steps, which shows as a visible jump in the step energies (nothing is hidden). The force is taken through the hcore and J/K objects the SCF built, which stay in memory for the force: their bytes are charged against the same memory budget ([memory] budget_gb with jk = "rsgdf"), and with jk = "dense" the kernels count against max_eri_gb while they exist. task = "frequencies" is an error.

The periodic run reads only [cell], [scf] max_iter, [scf] density_conv (Gamma point) or energy_conv (k-point mesh), [scf] level_shift (k-point rohf / ROKS only), [dft] functional, [memory] (RS-GDF only) and [optimize]. Any other section or key is an error. A charged cell is an error.

KeyTypeDefaultAllowed valuesNotes
lattice3×3 float arrayrequiredrows are the lattice vectorsIn unit.
unitstring"angstrom"angstrom bohrApplies to lattice, omega and gdf_omega (as their inverse), neighbour_cutoff and neighbour_switch.
kmesh[n1, n2, n3]none (Gamma point)each ≥ 1Selects the k-point drivers: rhf, uhf, rohf (no functional); Kohn-Sham (ksdft, or rhf/uhf/rohf with [dft] functional): RKS with LDA, GGA, global-hybrid and SCAN / r2SCAN / TPSS functionals (and range-separated (CAM) GGA hybrids such as HSE06, HYB_GGA_XC_CAM_B3LYP and HYB_GGA_XC_WB97X, with either jk; forces and stress for HSE06 and CAM-B3LYP with either jk, wB97X energy only), UKS (ksdft with multiplicity > 1 included) with the same functionals as RKS except TPSS (SCAN / r2SCAN energy with either jk, forces and stress with jk = "dense"; the range-separated hybrids with either jk: the energy of any libxc CAM GGA hybrid, HSE06 and CAM-B3LYP pinned, open-shell wB97X accepted, not pinned, and the force and stress of HSE06 / CAM-B3LYP), and ROKS with LDA, GGA, global-hybrid and range-separated hybrid functionals (either jk; range-separated forces and stress for HSE06 / CAM-B3LYP as for UKS) and the SCAN / r2SCAN energy (jk = "dense"); rimp2 and pdep-rpa. task = "optimize" and stress = true run with HSE06 / CAM-B3LYP on RKS, UKS and ROKS (either jk) and with TPSS and UKS SCAN / r2SCAN on jk = "dense". Not yet supported with kmesh, each an error naming the gap: ROKS meta-GGA forces and stress, a ROKS meta-GGA with jk = "rsgdf", task = "optimize" or stress = true with a range-separated hybrid other than HSE06 / CAM-B3LYP, a UKS meta-GGA force or stress with jk = "rsgdf", VV10 functionals, TPSS on UKS/ROKS, task = "optimize" or stress = true with TPSS on jk = "rsgdf", meta-GGAs other than the pure SCAN / r2SCAN / TPSS, and task = "optimize" for rimp2/pdep-rpa (see task). Open-shell k-point KS and ROHF use ewald_start; [cell] n_radial/n_angular/neighbour_cutoff apply to the Kohn-Sham routes and, with a 1×1×1 mesh, the runs equal the Gamma-point ones.
centringstring"gamma"gamma mpRequires kmesh. gamma is the Gamma-centred mesh (contains Γ; PySCF make_kpts(n)); mp is the shifted Monkhorst-Pack mesh (PySCF with_gamma_point=False). Energies, task = "optimize" forces and stress = true all accept either.
exxdivstring"ewald"ewald noneExchange G = 0 treatment.
jkstring"dense"dense rsgdfdense is a toy-scale dense AFT tensor.
auxbasisstringnonebundled basis nameRequired by jk = "rsgdf"; an error with dense.
range_splitbool or floatfalsefalse, true (λ = 1), or a number > 0 (λ)Opt-in RS-GDF range split: moves the short-range blocks whose Fourier transform converges in the long-range G sphere into G space. Orbital primitives with exponent ≤ λω²/2 and aux primitives with exponent ≤ λω² count as smooth, where ω is the RS-GDF split (gdf_omega, default 1 Bohr⁻¹; not the omega key). λ ≤ 1 adds no G vectors; the energy matches the unsplit build to fitting precision. Requires jk = "rsgdf" and task = "energy" (Gamma point or kmesh). An error with task = "optimize": the optimizer is not wired to the range-split forces.
omegafloatmin(2.5 √π / V^(1/3), 0.9636 × ω_gdf), ω_gdf = gdf_omega in Bohr⁻¹> 0, in unit⁻¹Nuclear-attraction Ewald split. Any value gives the same energy to truncation (~1e-11 Ha); the default balances the real- and reciprocal-space nuclear-attraction cost, and its cap keeps the reciprocal-space sphere inside the RS-GDF one (so the cap scales with gdf_omega).
gdf_omegafloat or "auto"1 Bohr⁻¹finite, > 0, in unit⁻¹; or "auto"RS-GDF Ewald split ω. Any value gives the same energy to the fitting precision; it moves work between the short-range lattice sums (radii ∝ 1/ω) and the long-range G sphere (|G| ≤ 2ω √ln(10¹³)). The range_split thresholds and the RS-GDF forces follow it. Requires jk = "rsgdf" (an error with dense). Same as Python gdf_omega= (Å⁻¹). "auto" (opt-in; the default is unchanged) picks ω from a cost model of the cell: it prices the short-range triplet walk (the build's own screen, sampled) against the long-range G sum at ω = 0.25..2 Bohr⁻¹ and leaves 1 Bohr⁻¹ unless another value is predicted at least 10% cheaper. On the study cells (Gamma RHF, cc-pVDZ / cc-pVDZ-RI, 6 threads) sparse molecular crystals run 1.4-2.4× faster (dry ice 20.0 → 11.8 s) and dense cells keep 1 Bohr⁻¹ at a cost of ~0.2-0.5 s for the choice. Gamma-point task = "energy" only; an error with kmesh and task = "optimize". The chosen value is printed in the header and on stderr. Python: ferric.auto_gdf_omega(...) returns the value to pass as gdf_omega=.
sr_column_rotationboolon for a jk = "rsgdf" run (Gamma or kmesh), off with jk = "dense"true falseColumn rotation of generally contracted shells (the separate s/p contraction columns of cc-pVXZ-style bases) in the short-range walks: the RS-GDF short-range 3-centre sum and the hcore short-range nuclear attraction run on rotated columns and are transformed back exactly (at a kmesh the back-transform acts on the short-range residue bins and on V_SR(k) before the Bloch phase sums, the long-range and G = 0 parts stay in the parent basis). The energy matches the unrotated build to the screening precision (≤ 1.5e-11 Ha/cell at gdf_omega ≥ 0.7 on dry ice and diamond, cc-pVDZ) with fewer short-range integral calls (dry ice 25.9 → 22.4 s, diamond 7.84 → 3.08 s at 6 threads, gdf_omega = 1, range_split = true). The analytic forces and stress (task = "optimize") differentiate the same rotated walks, with the derivative blocks evaluated on the rotated columns, so optimisations keep the saving; at the Gamma point forces and stress equal the unrotated ones to ≤ 1e-12 (Ha/Bohr, Ha) on the test cells; at a kmesh the gap to the unrotated derivative is set by the screen (force 1.5e-10 Ha/Bohr, stress 6.5e-9 Ha on H2 cc-pVDZ at hcore/fit precision 1e-9/1e-10, shrinking with the precision). A basis with nothing to rotate (STO-3G, Pople, segmented) runs the unrotated build bit for bit. Absent, it is on for a jk = "rsgdf" run (Gamma point or kmesh) and off with jk = "dense"; the header line sr_column_rotation = says which and why. false turns it off everywhere. true is an error with jk = "dense". The k-point analytic forces (task = "optimize" with kmesh) and the library's k-point stress differentiate the rotated walks (the derivative blocks on the rotated columns, the residue weights transformed with Tᵀ W T). The stage table counters report how many columns were rotated: hcore SR rotated columns and rsgdf SR3 rotated columns at the Gamma point, k hcore SR rotated columns and k rsgdf SR3 rotated columns at a kmesh (0 = unrotated). Same as Python sr_column_rotation= on the Gamma bindings (None = this default).
max_eri_gbfloat0.5> 0Dense tensor cap. An error with rsgdf.
ewald_startstring"staged"staged directOpen-shell SCF with exxdiv = "ewald" only.
denominatorsstringrequired for rimp2/pdep-rpashifted unshiftedAn error on other kinds.
frozen_coreinteger0≥ 0rimp2/pdep-rpa only.
quad_pointsinteger40≥ 1pdep-rpa only. Gamma point needs jk = "rsgdf".
drpa_energystring"quadrature"quadrature plasmon second-orderpdep-rpa with kmesh only.
grad_convfloat1e-9> 0k-point SCF orbital-gradient threshold. Requires kmesh.
stressboolfalsetrue falseCompute and print the analytic stress σ = (1/Ω) dE/dε of the converged k-point SCF (Hartree/Bohr³ and GPa) and write it to the run_end record. Requires kmesh and task = "energy"; rhf, uhf, rohf, ksdft and the [dft] functional routes. An error, naming [cell] stress = true, with the Gamma point, task = "optimize", rimp2/pdep-rpa, range_split, range-separated hybrids other than HSE06 / CAM-B3LYP, TPSS and UKS SCAN/r2SCAN on jk = "rsgdf" (HSE06 / CAM-B3LYP run on RKS, UKS and ROKS with either jk; closed-shell TPSS and UKS SCAN/r2SCAN on jk = "dense"). ECP cells are supported. ROKS meta-GGA and hybrid meta-GGA are refused earlier by the k-point energy gate ([cell] kmesh with functional ...), which does not name stress. The run does one SCF through the retained hcore and J/K (the iteration printout of the plain energy run is replaced by the iteration count, energy and stress). The SCF must converge (the stress gate for restricted open shell is loose, 1e-5 in the orbital gradient: keep grad_conv well below it).
n_radialinteger75Periodic XC grid. Kohn-Sham only.
n_angularinteger3026 14 26 50 110 194 302 434 590Periodic XC grid. 434 and 590 are refinement grids; any other order is an error. Kohn-Sham only.
neighbour_cutofffloatmax(10 Bohr, covering-radius bound) (with neighbour_switch W: that value + W)> 0, in unit; ≥ bound + WPeriodic XC grid image cutoff D: image atoms within D of a grid point enter its partition weight. Kohn-Sham only. With the default neighbour_switch = 0 the cutoff is hard: the energy jumps wherever an image crosses D under a displacement or strain (measured: up to 2.2e-7 Ha on a (20, 50) grid at D = 10 on an H2 cell, and a 3.6e-5 Ha jump on an s+p triclinic cell at D = 10; the size depends on the grid and the cell) (a larger D does not remove the jumps; they get denser), and the analytic force and stress do not see the jumps, so a finite-difference step that straddles one misses by the jump / 2h. The default D also depends on the atom positions in a large sparse cell, so set it explicitly for task = "optimize" and for finite-difference checks.
neighbour_switchfloat0 (hard cutoff)≥ 0, in unitWidth W of a smooth taper on the image cutoff: an image at distance d from a grid point takes part with presence 1 for d ≤ D − W, falling to 0 at D (quintic smoothstep), and the analytic force and stress include the taper's derivative. This makes the energy continuous under displacement and strain. The taper costs the energy what lowering the cutoff toward D − W would (the automatic D adds W for that reason). Cost: the automatic D grows by W, so the image neighbours per point rise by about (1 + W/D)³; measured grid build on Li–H (75, 302): 0.29 s hard at D = 10, 0.87 s with W = 2 at the automatic D = 12. 0 is the hard cutoff, unchanged. Kohn-Sham only. Python: the neighbour_switch keyword (Å, default 0.0) of run_rks_gamma, run_uks_gamma, run_roks_gamma, run_rks_kpts, run_uks_kpts and run_roks_kpts; a negative or non-finite value is a ValueError.

Periodic [scf] defaults are max_iter = 200, density_conv = 1e-10 and energy_conv = 1e-12, not the molecular ones. k-point ROKS with a hybrid (exact exchange, global or range-separated) applies a 0.05 Ha virtual level shift by default, ramped to zero at convergence so the converged energy is unchanged; LDA/GGA ROKS and ROHF get none. [scf] level_shift (finite, ≥ 0, Hartree) overrides it on the k-point rohf / ROKS routes; 0 turns it off. Any other periodic route refuses the key.

Python bindings

import ferric gives you the same engine as the ferric CLI, as plain function calls that return result objects holding floats and numpy arrays. To install it, see Installation. The wheel is enough; you do not need a clone of the repository to run anything on this page.

If you already know PySCF, For PySCF users maps the calls you know onto ferric's and lists where the two behave differently.

Every snippet below uses an inline geometry. Run them in order, since later ones reuse water, o2, bs, bs_dz and aux from earlier ones. The snippets through "Properties and charges" were run when this page was written (2026-09-23), and any output shown is what they printed. The one-line calls in the reference tables were not run.

Molecules and basis sets

import ferric

water = ferric.Molecule.from_xyz_string("""3
water
O   0.000000   0.000000   0.117790
H   0.000000   0.755453  -0.471161
H   0.000000  -0.755453  -0.471161
""")
bs = ferric.BasisSet.bundled("sto-3g")

The string is standard XYZ: an atom count on the first line, a comment line, then one symbol x y z line per atom. The count must be the first line, so start the string with """3. Starting it with """ and a newline gives an empty first line, and parsing fails with bad atom count. Molecule.from_xyz(path) reads the same format from a file. A leading @ on a symbol (@H) makes a ghost atom, which carries basis functions but no nucleus or electrons.

Charge and spin belong to the molecule, not to the SCF call:

o2 = ferric.Molecule.from_xyz_string("""2
O2 triplet
O 0.0 0.0 0.0
O 0.0 0.0 1.208
""", charge=0, multiplicity=3)

multiplicity is 2S+1, so a triplet is 3. (PySCF's spin is 2S, so the same triplet there is spin=2.) Both constructors default to charge=0, multiplicity=1. run_uhf and run_rohf take no spin argument; they read it from the molecule. The geometry-changing drivers (run_frequencies, run_saddle, run_irc) also accept a multiplicity= keyword.

Units

QuantityUnit
XYZ input to from_xyz / from_xyz_stringÅngström
Molecule.coords()Ångström
Molecule.coords_bohr()Bohr (ferric stores Bohr internally)
point_charges= / smeared_charges= on run_rhf, run_uhf, run_rohf, run_dft, …Bohr, charges in e
external_field=Hartree atomic units
esp_at_points(..., points)points in Bohr; potential in atomic units
omega on run_attenuated_rimp2, run_rs_mp2_rpaÅ⁻¹ (default 0.420)
r0 on the terfc driversÅ
omega / r0 on compute_eri3_mo, compute_metric_2cBohr⁻¹ / Bohr (raw, unlike the run_* drivers)
QmmmSystem(..., coords_angstrom)Ångström
QmmmSystem.point_charges()Bohr
EnergiesHartree
GradientsHartree/Bohr
print(water.coords()[0])        # (0.0, 0.0, 0.11779)            Ångström
print(water.coords_bohr()[0])   # (0.0, 0.0, 0.22259084021251865) Bohr

The Sharp bits page has the full units table, including the QM/MM accessors.

Bundled basis sets

BasisSet.bundled(name) loads a basis compiled into the library. Names are case-insensitive. An unknown name raises ValueError listing the known ones; BasisSet.bundled_names() returns them, and bs.elements() gives the atomic numbers a set covers. These 64 sets are available:

KindNames
Poplesto-3g, 6-31g, 6-31g*, 6-31g**, 6-31+g*, 6-31+g**, 6-31++g**, 6-311g**, 6-311++g**
Dunningcc-pvdz, cc-pvtz, cc-pvqz, aug-cc-pvdz, aug-cc-pvtz, aug-cc-pvqz
Dunning with ECP (Z >= 29; lighter elements are the all-electron set)cc-pvdz-pp, cc-pvtz-pp, cc-pvqz-pp, aug-cc-pvdz-pp, aug-cc-pvtz-pp, aug-cc-pvqz-pp
Karlsruhe (ECP inline for Z >= 37)def2-svp, def2-svpd, def2-tzvp, def2-tzvpp, def2-tzvpd, def2-tzvppd, def2-qzvp, def2-qzvpp, def2-qzvppd
Jensenpcseg-0, pcseg-1, pcseg-2, aug-pcseg-1, aug-pcseg-2
Explicitly correlated (F12)cc-pvdz-f12 (orbital), cc-pvdz-f12-optri (OptRI auxiliary)
RI (MP2/RPA/CC fitting)cc-pvdz-rifit, cc-pvtz-rifit, cc-pvqz-rifit, aug-cc-pvdz-rifit, aug-cc-pvtz-rifit, aug-cc-pvqz-rifit, the six <set>-pp-rifit partners of the ECP sets, and def2-<x>-rifit for each of the nine def2 sets
JK (SCF fitting)def2-universal-jkfit, cc-pvtz-jkfit, cc-pvqz-jkfit, aug-cc-pvdz-jkfit, aug-cc-pvtz-jkfit, aug-cc-pvqz-jkfit

Aliases: the Pople stars also load as 6-31g(d), 6-31g(d,p), 6-31+g(d), and so on; every RIFIT set also loads as <orbital>-ri and ORCA's <orbital>/c (so cc-pvdz-ri is cc-pvdz-rifit); the JKFIT sets as <orbital>/jk; and def2-universal-jkfit as def2/jk or PySCF's def2-<x>-jkfit.

The aug-cc-pv{d,t,q}z-jkfit sets (Weigend, PCCP 4, 4285 (2002)) are the one exception to "from the Basis Set Exchange": BSE does not carry them. They come from Molpro's weigend_jkfit.libmol via Psi4's library (commit pinned in provenance.json), checked bit-identical to PySCF's copy. Two caveats from that source: their Ne, Ar and Kr functions are extrapolated, not published, and aug-cc-pvdz-jkfit was never published at all; it is aug-cc-pvtz-jkfit with the highest angular momentum dropped. For the S66-type noncovalent work they exist for, aug-cc-pvtz-jkfit is the set to use with aug-cc-pVTZ.

Each set covers what its source covers, and an element it lacks is an error at basis setup that names the element and the covered range (for example cc-pvtz has H-Ar and Ca-Kr, with no K; cc-pvtz-jkfit and cc-pvqz-jkfit have only H, B-F, Al-Cl and Ga-Br; the -pp-rifit sets cover H-Ar (the aug ones without Li and Na), Sc-Ni at TZ and QZ only, Y-Pd and Hf-Pt). It is never patched silently. The Pople sets follow Gaussian's d convention: the 6-31G family has Cartesian d (6D), and the 6-311G family has spherical d (5D) on every element, including Na-Ar, which the Basis Set Exchange marks Cartesian. Everything else is spherical, as the Basis Set Exchange types it. See For PySCF users for matching PySCF. crates/ferric-core/src/basis/provenance.json records the source, version and element list of every set. BasisSet.from_bse_json(path) loads any other Basis Set Exchange JSON file.

Validation: every set is checked against PySCF fed the same basis (crates/ferric-mp2/tests/basis_coverage_vs_pyscf.rs, at most 1e-8 Ha). Orbital sets are checked by an RHF energy, RI sets by an RI-MP2 correlation energy, and JK sets also by a density-fitted (RI-J + RI-K) RHF energy. For the BSE sets, PySCF fetches the basis from BSE by name, so the comparison covers both the shipped digits and ferric's parsing and normalisation. For the three aug-JKFIT sets, PySCF uses its own copy of the data. The digits are covered instead by the import's Psi4-vs-PySCF comparison, and the energy comparison covers parsing and normalisation.

Most drivers take a BasisSet object. The drivers that move atoms (run_optimize, run_frequencies, run_saddle, run_irc, run_qmmm, run_optimize_qmmm) take the basis name as a string instead, because they rebuild the basis at every geometry.

Ground state

rhf = ferric.run_rhf(water, bs)
print(rhf)
print(f"RHF: {rhf.energy:.10f} Ha, converged={rhf.converged}")
RHF Energy: -74.9631468000 Ha (converged: true, 8 iterations)
RHF: -74.9631468000 Ha, converged=True

Open shell, using the triplet o2 built above:

bs_dz = ferric.BasisSet.bundled("cc-pvdz")
uhf  = ferric.run_uhf(o2, bs_dz)
rohf = ferric.run_rohf(o2, bs_dz)
print(f"UHF  {uhf.energy:.10f}  converged={uhf.converged}")
print(f"ROHF {rohf.energy:.10f}  converged={rohf.converged}")
UHF  -149.6276689907  converged=True
ROHF -149.6079865946  converged=True

PySCF 2.12 on the same geometry and basis gives −149.6276689907 (UHF) and −149.6079865946 (ROHF). run_rohf returns a UhfResult: it has α and β densities and orbital energies, which coincide for the spatial orbitals.

Kohn–Sham DFT:

dft = ferric.run_dft(water, bs_dz, functional="b3lyp")
print(f"B3LYP {dft.total_energy:.10f}")

run_dft is closed-shell only, and its default functional is LDA. It uses density fitting for Coulomb by default (def2-universal-jkfit); run_rhf does not. Pass df_j_aux="exact" for conventional four-centre Coulomb when you compare against an exact-Coulomb code. dispersion="d3bj" adds Grimme D3(BJ); the result then carries e_scf, e_dispersion and their sum in total_energy. with_gradient=True also returns the analytic nuclear gradient from dft.gradient(). There is no open-shell run_dft. Unrestricted Kohn–Sham is reachable from Python only inside other drivers: run_frequencies(reference="uhf", xc=...), run_u_gw(xc=...), run_qmmm(method="uks") and run_cdft(functional=...).

run_rhf also takes implicit solvent (solvent=78.4 or solvent="water", IEF-PCM), point charges and a uniform field. run_optimize and run_optimize_uhf take the same solvent and pcm_lebedev_order and follow the analytic IEF-PCM gradient; there is no Python COSMO option, and COSMO has no gradient. See its docstring (help(ferric.run_rhf)) for the full SCF knob set, which matches the CLI [scf] section.

Naming the convergence keywords

A bare max_iter, energy_conv or density_conv belongs to a driver whose method is the SCF itself (run_rhf, run_uhf, run_rohf, run_dft, run_ksdft, run_qmmm). Every other driver solves a reference SCF as one stage among others (MP2, CC, RPA, GW, TDDFT, orbital optimization, constrained DFT, frequencies, geometry optimizers, saddle and IRC searches), so its SCF knobs are scf_max_iter, scf_energy_conv and scf_density_conv, with the meaning and defaults of run_rhf's. Another stage's iteration knob is stage-named (oo_max_iter, max_outer, max_steps, eigensolver_*). The bare spellings that older releases accepted on run_cdft, run_frequencies and run_oo_rimp2 (max_iter there) still work, warn with a DeprecationWarning, and raise if combined with the prefixed one. The CLI keeps each key in its own section ([scf], [optimize], [gw], [rpa], [mp2] oo_*).

Check .converged, and know what it means

run_rhf, run_uhf, run_rohf and run_qmmm return a result whether or not the SCF converged. A non-converged result has converged = False, and its energy is a plausible, wrong number. run_dft, run_ksdft and the correlated drivers (MP2, CC, RPA, GW, TDDFT) behave differently: they raise if their reference SCF does not converge.

converged = True means the SCF reached a stationary point. It does not mean the lowest one. The O2 triplet above with sto-3g instead of cc-pvdz shows this. run_uhf with the default MINAO guess converges to −147.63397 Ha, which is a saddle of the orbital Hessian 1.33 mHa above the UHF minimum at −147.635296 Ha (PySCF's default guess lands on the same saddle). Without stability_descent nothing in the result flags it. run_uhf(o2, bs, stability_descent=True) (CLI: [scf] stability_descent = true on kind = "uhf") follows the downhill eigenvector and reaches the minimum. guess="hcore" converges (converged=True) to a much higher stationary point, −147.3789 Ha, which lies above ferric's own ROHF (−147.63219 Ha). A UHF energy above the ROHF energy for the same molecule cannot be a ground state, so comparing the two is a cheap check for open-shell work.

Correlation

Correlated drivers run their own reference SCF internally, so they take the molecule and basis rather than an SCF result. They also take an explicit auxiliary (RI) basis. There is no automatic choice.

aux = ferric.BasisSet.bundled("cc-pvdz-ri")

rhf = ferric.run_rhf(water, bs_dz)
mp2 = ferric.run_rimp2(water, bs_dz, aux)
cc  = ferric.run_ccsd_t(water, bs_dz, aux)

print(f"RHF      {rhf.energy:.10f}")
print(f"RI-MP2   {mp2.total_energy:.10f}  (corr {mp2.mp2_corr:.10f})")
print(f"CCSD(T)  {rhf.energy + cc.correlation_energy + cc.t_correction:.10f}  total")
print(f"         {cc.correlation_energy:.10f}  CCSD correlation")
print(f"         {cc.t_correction:.10f}  (T)")
closed-shell CCSD converged in 10 iterations. E_corr = -0.2135061893
RHF      -76.0267679974
RI-MP2   -76.2308014541  (corr -0.2040334567)
CCSD(T)  -76.2433412449  total
         -0.2135061893  CCSD correlation
         -0.0030670582  (T)

The first line is progress output that the CCSD solver prints to stdout.

CcResult holds only correlation_energy and t_correction (which is None for run_ccd and run_ccsd). It carries no reference energy, so the total above adds run_rhf(...).energy by hand. The MP2-family results all carry a total_energy, and most also carry the reference energy.

Other members of the family use the same call shape:

att   = ferric.run_attenuated_rimp2(water, bs_dz, aux, omega=0.420)  # Å⁻¹
scs   = ferric.run_scs_mp2(water, bs_dz, aux)
sos   = ferric.run_laplace_sos_mp2(water, bs_dz, aux)
oo    = ferric.run_oo_rimp2(water, bs_dz, aux)
mp3   = ferric.run_mp3(water, bs_dz, aux)

frozen_core= is accepted by every correlated driver. run_terfc_rimp2 has no CLI method.kind of its own (the CLI reaches it through att-rimp2 with att_operator = "terfc"); the reference table below marks the CLI kind of every driver. run_rimp2, run_drpa and run_linlccd compute their method exactly unless local="amplitude-threshold" and eps= are passed (see Exact and local correlation).

Response and excited states

rpa   = ferric.run_pdep_rpa(water, bs_dz, aux)            # RPA correlation
gw    = ferric.run_gw(water, bs_dz, aux)                  # G0W0@HF by default
tddft = ferric.run_tddft(water, bs_dz, aux, n_roots=3, method="tda")

print(rpa.total_energy, rpa.eigensolver_converged)
print(gw.mo_indices, gw.eps_qp)          # quasiparticle energies, Hartree
print(tddft.excitation_energies)         # Hartree

run_gw runs method="g0w0" on an HF reference unless you pass xc= (for example xc="pbe"); by default it corrects HOMO−2 through LUMO+2. Check gw.outer_converged and gw.qp_converged before using the numbers. run_u_gw is the open-shell version.

run_tddft is closed-shell only. With functional=... it includes the (ia|f_xc|jb) exchange-correlation kernel; VV10 and range-separated functionals are refused because their kernel is not built. A τ-only meta-GGA (SCAN, r2SCAN, TPSS) defaults to a 150 × 434 grid; result.grid reports the grid the reference SCF and the kernel shared, and result.certificate lists it as response-grid. With no functional, it is CIS (method="tda") or TDHF (method="casida"). Both methods match PySCF TDA/TDDFT to 1e-3 eV on the systems listed in Capabilities and validation.

Properties and charges

The property functions take the molecule, the basis and a converged RhfResult or DftResult. They work on closed-shell results.

import numpy as np

rhf = ferric.run_rhf(water, bs)                      # water / STO-3G

q_lowdin = ferric.lowdin_charges(water, bs, rhf)
q_resp   = ferric.resp_charges(water, bs, rhf)
print(np.round(q_lowdin, 4), np.round(q_resp, 4))

# ESP 3 Bohr above each atom. Points are an (N, 3) array in Bohr.
pts = np.array(water.coords_bohr()) + np.array([0.0, 0.0, 3.0])
print(np.round(ferric.esp_at_points(water, bs, rhf, pts), 6))
[-0.2525  0.1263  0.1263] [-0.6176  0.3088  0.3088]
[-0.064684 -0.067154 -0.067154]

The charge family is mulliken_charges, lowdin_charges, hirshfeld_charges, chelpg_charges and resp_charges, all returning one charge per atom in units of e. hirshfeld_charges builds its proatoms from free-atom SCF densities in the molecule's basis, as the CLI does; proatom="slater" selects the single-exponential Slater proatom instead, which is 0.23–0.72 e away on H2O, CO and CH3OH. resp_charges is a single-stage restrained fit, not the multi-stage, multi-conformer RESP procedure. esp_at_atoms gives the potential at each nucleus. hirshfeld_polarizability returns per-atom 3×3 polarizability tensors (Bohr³) and needs an RI basis.

Orbitals, density and cube files

eval_ao_on_points, eval_density_on_points and eval_mo_on_points evaluate basis functions, the total electron density and restricted MOs at an (N, 3) array of points. Points and cube grids are in Bohr, like esp_at_points. eval_ao_on_points returns an (N, nbasis) array (PySCF eval_gto layout); eval_mo_on_points takes 0-based mo_indices into mo_coefficients and returns (N, len(mo_indices)). Orbital signs are the SCF's arbitrary phase. write_cube writes the density (e/Bohr³) or one MO (what="mo", mo_index=) on an axis-aligned grid, from the atoms' bounding box plus padding unless origin and shape are given, and returns the (origin, shape) used.

import numpy as np

rhf = ferric.run_rhf(water, bs)
pts = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]])    # Bohr
print(np.round(ferric.eval_density_on_points(water, bs, rhf, pts), 4))

origin, shape = ferric.write_cube("homo.cube", water, bs, rhf,
                                  what="mo", mo_index=4, spacing=0.3)
print(shape)
[10.2392  0.8103]
[28, 38, 32]

For NPZ export of ML-ready features (MO coefficients, PDEP eigenvectors, ESP, charges, polarizabilities, C6 coefficients) in one run, use the CLI's [rpa] export_npz section; see Input file (TOML).

What comes back

Results are Python objects with plain attributes for scalars and methods for arrays:

D = rhf.density()             # numpy.ndarray, (n_bf, n_bf), AO basis
e = rhf.orbital_energies()    # numpy.ndarray, ascending, Hartree
C = rhf.mo_coefficients()     # numpy.ndarray, (n_bf, n_mo)

Matrices and tensors come back as numpy.ndarray. Per-atom lists (charges, ESP values) come back as Python lists; wrap them in np.asarray if you need arrays. run_drpa, run_linlccd and tune_omega return plain dicts, and run_drpa_scan returns a list of them.

AO-basis matrices follow libint2's basis-function conventions, which are not PySCF's. Measured on CO/cc-pVDZ: total and orbital energies agree with PySCF to 3e-12 and 8e-9 Ha, and the two AO density matrices differ element by element by up to 1.5. Compare invariant quantities, not raw AO matrices.

Building blocks

ferric.AoIntegrals(mol, basis_set) exposes the integrals and J/K builds the drivers use, as numpy arrays, so a method can be written in numpy the way it is on top of PySCF's get_hcore / get_jk / ao2mo. Bohr and Hartree throughout; AOs are in ferric's shell order (shell_offsets()).

BlockReturns
overlap(), kinetic(), nuclear(), ecp(), hcore(), dipole(origin)(nao, nao); hcore() includes the ECP; dipole is (3, nao, nao)
jk(D)(J, K) of the TOTAL density, direct and Schwarz-screened: the RHF Fock matrix is hcore() + J - 0.5 * K
df_jk(aux)a DfJk holding the RI-J/RI-K fit, reused for every .jk(D)
eri()dense (μν|λσ), chemists' order, refused above max_gb (default 2 GiB)
eri3(aux), eri2(aux)raw (P|μν) as (naux, nao, nao) and the metric (P|Q)
df_mo(aux, C1, C2)fitted B^P_pq, (naux, k1, k2), with (pq|rs) ≈ Σ_P B^P_pq B^P_rs (the RI-MP2 fit)
ao2mo(C1, C2, C3, C4)exact (pq|rs), integral-direct, never holding the nao⁴ AO tensor
grad_*, hess_*, cphf, xc_functionalnuclear-derivative, response and XC pieces
ferric.einsum(spec, a, b)the contraction engine of ferric's correlation codes (grammar)

Every two-electron method takes operator="coulomb" | "erf" | "erfc" with omega in Bohr⁻¹. Orbitals come from the result objects (mo_coefficients(), orbital_energies(), density()) or from a numpy SCF on the blocks.

ints = ferric.AoIntegrals(water, bs)
J, K = ints.jk(D)
F = ints.hcore() + J - 0.5 * K
E = 0.5 * np.sum(D * (ints.hcore() + F)) + ints.nuclear_repulsion()
print(abs(E - rhf.energy) < 1e-8)
B = ints.df_mo(aux, C[:, :5], C[:, 5:])    # (naux, nocc, nvir)
print(B.shape)
True
(84, 5, 2)

Three methods written this way are in examples/python/ and are tested against the Rust drivers: rhf_from_blocks.py (DIIS RHF from hcore() and jk(), equal to run_rhf and PySCF to 1e-8 Ha), rimp2_from_blocks.py (RI-MP2 from df_mo, equal to run_rimp2 and PySCF DFMP2 to 1e-8 Ha) and cis_from_blocks.py (singlet CIS from df_mo or ao2mo, equal to run_tddft(method="tda") and PySCF TDA).

Not exposed: the Rust-side structs (PreparedBasis, Engine, screening bounds), method intermediates (CC amplitudes, GW self-energies, PDEP eigenpairs) and periodic (PBC) integrals. Those stay behind the drivers.

Memory, threads and MPI

Most drivers accept memory_budget_gb (GiB). It sets the same per-allocation limits as the CLI's [memory] budget_gb: an allocation that does not fit is spilled to disk, recomputed on demand (the DFT grid AO cache) or refused with an error naming it (for example run_rimp2 and run_ccsd). Unlike the CLI, Python installs no shared ledger, so each check compares its own allocation with the whole budget rather than with what other live allocations have left. Two checks instead subtract the process's current resident memory (RSS) first and allow 90% of the remainder: the KS-DFT decision to store or recompute the grid AO cache, and the UKS Newton/TRAH fxc kernel's second grid cache. Because RSS includes everything already resident, those two see less than the full budget. It does not cap total process memory; see Sharp bits.

mp2 = ferric.run_rimp2(water, bs_dz, aux, memory_budget_gb=8.0)

import ferric pins OpenBLAS to one thread unless OPENBLAS_NUM_THREADS is already set. ferric parallelizes with rayon instead. run_rhf, run_uhf, run_rohf, run_dft, run_rimp2, run_ccsd, run_ccsd_t, run_gw and run_tddft release the GIL, so independent jobs submitted from a ThreadPoolExecutor run in parallel. The other drivers hold it. For throughput across many molecules, prefer many single-threaded processes.

Do not run a Python script under mpirun. The bindings expose no rank or world-size accessor, so every rank runs the whole script. Distributed-memory runs go through the CLI built from source with MPI; see Installation.

Full reference

The module registers 61 public functions and 39 classes. That count excludes _cli_main, the entry point behind the ferric console command. It also exports two constants: DEFAULT_TEMPERATURE_K (298.15) and BOLTZMANN_HARTREE_PER_K. The list below was taken from the registration block of #[pymodule] fn ferric in crates/ferric-python/src/lib.rs, and each purpose line is condensed from that item's doc comment, or from its code where it has none. help(ferric.<name>) shows the full docstring and signature.

"CLI" gives the matching method.kind, task or TOML section, or "—" when the capability is Python-only. How well each one is validated is on Capabilities and validation.

Molecules and basis sets

NamePurposeCLI
MoleculeGeometry, charge and multiplicity. from_xyz, from_xyz_string, coords, coords_bohr, symbols, atomic_numbers, is_ghost, natoms, nelec, nuclear_repulsion, to_xyz_string.[molecule]
BasisSetA Gaussian basis set, orbital or auxiliary. BasisSet.bundled(name).[basis]

SCF and DFT

NamePurposeCLI
run_rhfClosed-shell RHF with the full SCF knob set, point charges, field and IEF-PCM solvent.rhf
run_uhfUnrestricted HF; α/β counts come from the molecule's charge and multiplicity.uhf
run_rohfRestricted open-shell HF (Guest–Saunders coupling); returns a UhfResult.rohf
run_dftClosed-shell Kohn–Sham DFT (LDA/GGA/hybrid/RSH/meta-GGA by name), optional dispersion (dispersion="d3bj" or "mbd", added to the energy and, with with_gradient=True, to the gradient) and analytic gradient.ksdft
run_ksdftAlias of run_dft.ksdft
d3bj_energyGrimme D3(BJ) dispersion energy for a molecule and functional, in Hartree; s9=1.0 adds the ATM three-body term (default 0, off).[dft] dispersion
mbd_rsscs_energyMBD@rsSCS dispersion energy (Hartree) from per-atom Hirshfeld volume ratios and β (or a functional with a published β); returns MbdRsscsResult with the screened α₀, C6, R_vdW and ω.—
tune_omegaIP-based (Baer/Kronik) tuning of an RSH functional's ω (Bohr⁻¹); closed-shell neutral plus doublet cation. continuation=True seeds each evaluation from the nearest already-evaluated ω, which keeps the cation on one branch across an out-of-order golden-section search; branch_tol flags an evaluation whose cation spin-population asymmetry jumps. check_cation_stability (default True) runs the UKS internal-stability analysis on every cation: each eval dict carries cation_lambda_min and cation_stability, and a tuned ω whose cation is an internal saddle raises RuntimeError naming ω and λ_min. Functionals without an orbital-Hessian kernel (VV10 such as ωB97X-V, meta-GGA) are reported not_analysed with a stability_warning, never as stable.—
dft_grid_point_countNumber of points in the main KS grid run_dft would build for the molecule with the same grid_* kwargs, without running an SCF (shows what pruning saves).—
SymmetricRhfResultResult of run_rhf(symmetry=True) (point-group-blocked RHF): energy (of the symmetrized geometry), point_group, orbital_irreps, max_symmetrization_shift, irrep_occupations(), orbital_energies(), symmetrized_coords(), frame_transform(). density(), mo_coefficients() and gradient() raise: the AO matrices live in the rotated frame.
RhfResultResult of run_rhf: energy, converged, iterations, density(), orbital_energies(), mo_coefficients(), one_step_energy_drop and stationarity_tol (the converged exit is stationary when the drop is above -stationarity_tol; both None for a non-aufbau or unconverged exit).
UhfResultResult of run_uhf/run_rohf: α and β densities and orbital energies, one_step_energy_drop, stationarity_tol.
DftResultResult of run_dft: total_energy (= e_scf + e_dispersion), e_scf, e_dispersion (None when not requested), dispersion_model ("D3(BJ)", "MBD@rsSCS" or None), volume_ratios (MBD@rsSCS only), converged, exit_reason(), one_step_energy_drop, stationarity_tol, density(), gradient().

Constrained DFT

NamePurposeCLI
run_cdftConstrained UHF, or UKS when functional names a libxc functional other than "HF" (None and "HF", any case, give UHF): minimize the energy subject to fragment population constraints (Wu–Van Voorhis nested λ loop). Raises if the λ loop does not converge.—
CdftConstraintOne fragment constraint: atoms (0-based), target (a Becke electron population, not a net charge), kind = "charge" (Nα + Nβ) or "spin" (Nα − Nβ).—
cdft_couplingWu–Van Voorhis coupling H_ab between two converged single-"charge"-constraint states solved with the same geometry, basis, occupations and Hamiltonian.—
CdftResultenergy (without the constraint term), converged, scf_converged, lambdas, populations, targets, density_alpha(), density_beta(), weight_matrix(i).
CdftCouplingResulth_ab (sign is a phase convention), s_ab, e_a, e_b.

See Constrained DFT for a worked example.

Geometry, vibrations and reaction paths

NamePurposeCLI
run_optimizeRHF geometry optimization (basis by name); solvent / pcm_lebedev_order optimize on the IEF-PCM surface (run_optimize_uhf likewise).task = "optimize"
run_frequenciesHarmonic frequencies from the analytic Hessian (RHF, UHF, ROHF with exact J/K, or RI-J / RI-K) or finite differences of the analytic gradient; RHF/UHF/ROHF or their KS variants. Closed-shell KS (LDA/GGA, pure or global hybrid) uses the analytic moving-grid Hessian under the default hessian="auto" (explicitly: hessian="analytic-moving-grid"); other KS references use finite differences.task = "frequencies"
run_xtb_energy_gradientGFN1/GFN2/GFN-FF energy and gradient (Hartree, Hartree/Bohr). Present in every build; raises RuntimeError unless the extension was built with the xtb cargo feature (libxtb on the loader path).no CLI kind
run_xtb_optimizeGeometry optimization on a GFN-xTB surface with the run_optimize BFGS driver; max_steps bounds geometry steps, scc_max_iter the SCC at each geometry, and an unconverged SCC raises. Certificate is not graded.no CLI kind
run_xtb_frequenciesHarmonic frequencies on a GFN-xTB surface: central differences of the xTB gradient (6N evaluations), shared mass-weighting and translation/rotation projection.no CLI kind
run_saddleFirst-order saddle-point (transition-state) search by P-RFO; closed-shell. hessian="fd" (default) / "analytic" / "auto"; .hessian_source reports which ran.—
run_ircIntrinsic reaction coordinate in both directions from a saddle; closed-shell.—
OptimizeResultenergy, converged, steps, energy_trace, mol().
FrequencyResultFrequencies in cm⁻¹ (negative = imaginary), normal modes, cartesian_hessian, asymmetry diagnostic, hessian_source.
SaddleResultOutcome of a P-RFO search: geometry (Å), n_imaginary, imaginary_mode, is_transition_state().
IrcResultBoth directions of an IRC, plus the saddle they came from.
IrcBranchOne direction of an IRC walk.

QM/MM

NamePurposeCLI
QmmmSystemA QM/MM partition with link atoms and boundary-charge schemes (coordinates in Å).[qmmm]
MmTopologyExplicit-parameter AMBER-form MM force field; assigns no parameters itself.—
run_qmmmEmbedded SCF energy plus QM gradient, MM forces and full gradient.[qmmm] (energy)
run_optimize_qmmmOptimize a QmmmSystem; QM atoms always move, MM atoms per move_mm.—
QmmmResultenergy, qm_gradient(), mm_forces() (forces, not gradients), full_gradient().
QmmmOptimizeResultThe relaxed partition and its energy trajectory.

See QM/MM for a worked example.

MP2 family

NamePurposeCLI
run_rimp2RI-MP2 on an RHF reference; UHF + unrestricted RI-MP2 when multiplicity > 1 (reference says which). Exact by default; local=/eps= for the local approximation.rimp2
run_oo_rimp2Orbital-optimized RI-MP2 (level-shifted Newton + DIIS + Cayley rotation). Closed shell only; open-shell OO-RI-MP2 is CLI-only.oo-rimp2
run_mp3MP3 on an RHF reference, with RI integrals.mp3
run_attenuated_rimp2RI-MP2 with the erfc-attenuated operator; ω in Å⁻¹, default 0.420.att-rimp2
run_terfc_rimp2RI-MP2 with the exact tempered-erfc operator at one cutoff r0 (Å); needs the terfc tables.—
run_scs_mp2Spin-component-scaled MP2 (defaults c_OS = 6/5, c_SS = 1/3).scs-mp2
run_scs_mp2_2terfcDual-attenuated SCS-MP2(2terfc); needs the terfc tables.scs-mp2-2terfc
run_mp2_vMP2-V: attenuated MP2 plus damped VV10 nonlocal correlation.mp2-v
run_double_hybridB2PLYP or DSD-PBEP86 double hybrid; the result carries a certificate (Spike).b2plyp, dsd-pbep86
run_laplace_mp2Laplace-transform RI-MP2 (default 7 quadrature points).laplace-mp2
run_laplace_sos_mp2Laplace-transform SOS-MP2, E = c_os · E_OS; MO, AO or AO-sparse formulations.laplace-sos-mp2
RiMp2ResultResult of run_rimp2 and run_terfc_rimp2: total_energy, rhf_energy (the reference SCF energy, RHF or UHF), mp2_corr, reference, and local (None for the exact method, else the local model dict).
OoRiMp2ResultResult of run_oo_rimp2, with converged and grad_norm.
Mp3Resulte_hf, e_mp2, e_mp3, e_corr, e_total.
AttenuatedMp2ResultAttenuated MP2 total, correlation and spin components.
ScsMp2ResultResult of run_scs_mp2/run_scs_mp2_2terfc, with e_os/e_ss.
Mp2VResultMP2-V total, attenuated MP2 part and VV10 part.
LaplaceMp2ResultLaplace MP2 total, correlation and spin components.
SosMp2ResultScaled and unscaled OS energy, c_os, n_quad and formulation echoed back.
DoubleHybridResultResult of run_double_hybrid: total_energy, e_ks, e_corr_scaled, e_os, e_ss, c_os, c_ss, certificate.

run_rimp2, run_oo_rimp2, run_attenuated_rimp2, run_terfc_rimp2, run_scs_mp2, run_scs_mp2_2terfc, run_mp2_v, run_laplace_mp2, run_laplace_sos_mp2 and run_rs_mp2_rpa take scf_jk="exact"|"rijk"|"rijcosx" and jk_auxbasis= to choose how the reference SCF builds J and K, and their results echo scf_jk and jk_auxbasis. The default is "exact", except for run_rs_mp2_rpa, whose default is "rijk". run_oo_rimp2 accepts only "exact" and raises on the fitted modes, because it rebuilds exact J/K at every orbital step. See The MP2 family.

Exact and local correlation

run_rimp2, run_drpa and run_linlccd name a method and compute it exactly by default. The local approximation is asked for with keywords that mirror the CLI's [local] section, under the same rules, raised as ValueError before any SCF:

  • local= is None (exact; "none" is the same) or "amplitude-threshold"; any other value is an error.
  • eps= is required with local="amplitude-threshold": the threshold is part of the model and has no default. eps=0 reproduces the exact method. eps= without local= is an error, not ignored.
  • compute_reference=True (local only) also computes the exact method and reports it: local["e_corr_canonical_ri"] (MP2), e_corr_plasmon_canonical (dRPA), local["e_corr_exact"] (LinLCCD). Off, the key is present and None. It is a full exact calculation, so it removes any cost saving.
  • run_rimp2(integral_direct=True, ...) selects the integral-direct local MP2, with its locality maps aux_radius, virt_radius (Bohr), ao_tail, schwarz_skip, batch_merge, gate_cal and virt_schwarz_kappa (the CLI names and defaults). They are errors without integral_direct=True. kappa is an error on the local MP2.

Every result says which model it is: RiMp2Result.local and the dicts' "local" key are None for the exact method, else a dict with scheme, eps, keep_fraction and integral_direct plus solver counters. All local paths are closed shell.

NamePurposeCLI
run_drpadRPA@HF by the drCCD Riccati solve. Exact by default (equals the plasmon formula); MemoryError before the SCF when the exact solve cannot fit (use run_pdep_rpa(..., trunc_thresh=0)). diis= (default 8); eps_rtol_factor= (local only).drpa
run_drpa_scanThe local dRPA over a list of eps values, sharing one SCF and localization; each dict carries "local".drpa + [local] eps_sweep
run_linlccdLinearized ladder CCD, variant = "hh" (default), "drivers-only", "full". Exact by default.linlccd

Coupled cluster

All use RI integrals from the auxbasis argument and a closed-shell RHF reference.

NamePurposeCLI
run_ccdCCD correlation energy.—
run_ccsdSpin-adapted closed-shell CCSD.ccsd
run_ccsd_tCCSD plus the spin-adapted (T) correction.—
CcResultcorrelation_energy and t_correction (None without triples). No reference energy.

RPA, GW and excited states

NamePurposeCLI
run_pdep_rpaDirect RPA correlation energy by PDEP (projective dielectric eigenpotentials); accepts point charges, field and solvent. Closed shell only; open-shell U-PDEP-RPA is CLI-only.pdep-rpa
run_rs_mp2_rpaRange-separated SR-MP2 + LR-RPA (formulation = "delta-lr" or "coupled-rings"; ω in Å⁻¹).rs-mp2-rpa
run_gwClosed-shell G0W0 / COHSEX / evGW0 / evGW on an RHF or RKS reference.gw
run_u_gwOpen-shell GW variants on a UHF/UKS or ROHF reference.gw with multiplicity > 1
run_bse_tdaBSE-TDA singlet excitation energies on a closed-shell RHF reference.bse-tda
run_tdhf_static_polarizabilityRPAx@KS static (ω = 0) polarizability on a closed-shell KS reference.tdhf-static-polarizability
run_tddftTDA or Casida excitations on a closed-shell HF (CIS/TDHF) or KS reference, with the f_xc kernel.tda, tddft
PdepRpaResulttotal_energy, e_rpa, eigensolver_converged, eigenvalues and quadrature grid.
RsMp2RpaResultSR-MP2, LR-MP2 and dRPA pieces; which fields are set depends on formulation.
GwResulteps_qp, eps_mf, sigma_x, sigma_c, z_factor, outer_converged, qp_converged.
UGwResultα and β versions of the GwResult fields.
BseResultExcitation energies and oscillator strengths.
TdhfStaticPolarizabilityResultPolarizability tensor and its isotropic value iso.
TddftResultexcitation_energies, oscillator_strengths, method, grid, certificate.

Properties and charges

Each takes (mol, basis_set, result) with a converged closed-shell RhfResult or DftResult; esp_at_points also takes the points and hirshfeld_polarizability an RI basis.

NamePurposeCLI
esp_at_atomsElectrostatic potential at each nucleus, in atomic units.[rpa] compute_esp
esp_at_pointsElectrostatic potential at arbitrary points given in Bohr.[rpa] compute_esp_surface (vdW-surface points only)
mulliken_chargesMulliken population charges.[rpa] compute_mulliken_charges
lowdin_chargesLöwdin (symmetric-orthogonalization) charges.[rpa] compute_lowdin_charges
hirshfeld_chargesHirshfeld charges. The default proatom="scf" uses free-atom SCF densities in the molecule's basis, solved with the result's own SCF settings, as the CLI does; proatom="slater" uses a single-exponential Slater proatom.[rpa] compute_hirshfeld_charges
chelpg_chargesCHELPG charges fitted to the ESP on a grid.[rpa] compute_chelpg_charges
resp_chargesSingle-stage RESP (restrained ESP-fit) charges.[rpa] compute_resp_charges
hirshfeld_polarizabilityPer-atom Hirshfeld-partitioned static polarizability tensors (Bohr³) from PDEP-RPA.—
orbital_momentsPer-orbital centroids and spatial spreads (Bohr) of the restricted MOs.—
density_second_moment3×3 second-moment tensor of the electron density (Bohr²).—
eval_ao_on_pointsAO values at (N, 3) points in Bohr, shape (N, nbasis).—
eval_density_on_pointsTotal electron density (e/Bohr³) at points in Bohr.—
eval_mo_on_pointsRestricted MO values at points in Bohr, for chosen 0-based MO indices.—
write_cubeGaussian cube file of the density or one restricted MO; all lengths in Bohr.—

The [rpa] compute_* keys write into the CLI's NPZ export.

Integrals and orbitals (prototyping)

These take omega/r0 in raw Bohr units, unlike the run_* drivers.

NamePurposeCLI
AoIntegralsOne-electron matrices, ERIs (dense, 3- and 2-index, MO-transformed), J/K, gradient/Hessian/response/XC pieces for one molecule and basis; see Building blocks.—
DfJkFrom AoIntegrals.df_jk(aux): RI-J/RI-K j(D), k(D), jk(D) over a fit built once.—
XcFunctionalFrom AoIntegrals.xc_functional(name): E_xc, V_xc, f_xc response and XC gradient/Hessian on run_dft's grid.—
Cphf, UcphfFrom AoIntegrals.cphf(...) / ucphf(...): orbital-Hessian matvec, CPHF/CPKS solve, response density.—
einsumTwo-operand contraction under ferric's einsum spec grammar.—
compute_eri3Raw 3-centre Coulomb integrals (P|μν), shape (naux, n_bf, n_bf).—
compute_eri3_moMO-basis 3-centre integrals (P|pq) for any two coefficient matrices, built blockwise under a memory budget.—
compute_metric_2c2-centre metric (P|w|Q) over the auxiliary basis, Coulomb by default.—
shell_infoShell centres (Bohr), first-function offsets and sizes, for building fitting domains.—
boys_localizeFoster–Boys localization of given orbitals; returns a BoysResult.—
BoysResultResult of boys_localize: c_loc(), centers(), converged, iterations.

Conformer ensembles

NamePurposeCLI
ConformerEnsembleConformers of one species sharing atom order, composition, charge and multiplicity.—
BoltzmannWeightsBoltzmann populations of an ensemble at one temperature.—
EnsembleDiagnosticsPopulation-structure readout: effective number of conformers, dominance verdict.—
WeightedStatsA weighted mean with its spread (mean, std_dev, min, max).—
boltzmann_weightsBoltzmann weights from a list of energies (Hartree) at a temperature (default 298.15 K).—
weighted_statsWeighted mean and standard deviation of a scalar property.—
weighted_stats_vectorThe same, component-wise, for a vector property.—
weighted_stats_tensorThe same, element-wise, for a rank-2 tensor property.—

Examples

This page indexes every input file in examples/. There are 84 TOML files. Three Python scripts that build methods from AoIntegrals blocks are in examples/python/ (Python bindings). Run a TOML file from the repository root:

OPENBLAS_NUM_THREADS=1 cargo run --release --bin ferric -- examples/water-rhf.toml

What CI checks

CI parses every shipped example and runs the three listed below. The test all_shipped_examples_parse (in crates/ferric-cli/src/config.rs) loads every examples/*.toml through the strict parser. It also checks that each file's [molecule] xyz path exists. It does not execute the calculation or compare any energy.

Three examples are also executed by integration tests in crates/ferric-cli/tests/:

ExampleTestWhat the test asserts
water-rhf.tomlepistemic_warning.rs, verbose_trace.rs, qmmm_reports_the_pqr_as_its_geometry.rsThe run completes and prints no [warning] grade line.
water-tdhf-static-alpha.tomlepistemic_warning.rsThe Smoke-grade warning goes to stderr and not to stdout.
water-qmmm.tomlqmmm_reports_the_pqr_as_its_geometry.rsThe embedded energy -74.9653197421 appears in the output.

Every other number in the "Reference in header" column below is copied verbatim from that file's comment block, and CI does not re-check it. Where the header cites a Rust test as the reference, that test is the check.

Examples that need extra setup

  • water-mp2v.toml, water-scs-mp2-2terfc.toml, water-attmp2-terfc.toml and any rs-mp2-rpa run with attenuator = "terf" need the tempered-erfc interpolation tables: point FERRIC_TERF_TABLE_DIR at them. Without it these runs stop with an error.
  • benzene-dfb3lyp-mpi.toml is an ordinary input; its header gives the --features mpi build and the mpirun launch line.

SCF and DFT

See SCF and DFT.

FileSystem / basiskind / taskReference in headerNotes
water-rhf.tomlH2O / STO-3Grhf / energy—water-qmmm.toml's header gives this geometry's energy as −74.9631468000. The file is commented to demonstrate screening.
benzene-rhf.tomlbenzene / cc-pVDZrhf—Exact 4-index J/K.
benzene-rhf-dfj.tomlbenzene / cc-pVDZrhf—RI-J only (cc-pvdz-ri).
benzene-rhf-rijk.tomlbenzene / cc-pVDZrhf—RI-JK (def2-universal-jkfit).
benzene-rhf-def2.tomlbenzene / def2-SVPrhf—
benzene-rhf-def2-rijk.tomlbenzene / def2-SVPrhf—RI-JK.
decane-rhf.tomldecane / STO-3Grhf—k_builder = "link".
water-rhf-cosx.tomlH2O / cc-pVDZrhf"E(COSX) − E(direct) is reported in crates/ferric-scf/tests/cosx_scf.rs"COSX exchange.
water-rhf-symmetry.tomlH2O / STO-3Grhf + [scf] symmetry—Point-group-blocked RHF (C2v); irrep_nelec shown commented.
h_uhf.tomlH atom / STO-3G, doubletuhf—
h2_opt.tomlstretched H2 / STO-3Grhf / optimize—
water-frequencies.tomlH2O / STO-3Grhf / frequencies—Finite-difference Hessian (hessian = "fd"). Check Hessian asymmetry.
nh2-rohf-frequencies.tomlNH2 doublet / cc-pVDZrohf / frequenciescrates/ferric-scf/tests/validation_rohf_hessian.rsAnalytic ROHF Hessian.
benzene-dfb3lyp.tomlbenzene / def2-SVPksdft B3LYP—RI-JK is on automatically for ksdft.
benzene-dfb3lyp-mpi.tomlbenzene / cc-pVDZksdft B3LYP—Run under mpirun with the MPI build (see its header).
water-wb97xv.tomlH2O / cc-pVDZksdft wB97X-V—
water-pbe-d3bj.tomlH2O / cc-pVDZksdft PBE + D3(BJ)—Prints E(KS-DFT) and E(D3BJ) separately.
water-pbe-mbd.tomlH2O / cc-pVDZksdft PBE + MBD@rsSCS—Prints E(KS-DFT) and E(MBD@rsSCS) separately.
water-pbe-pruned-grid.tomlH2O / cc-pVDZksdft PBE / energy"removes ~23% of the grid points" (at 75×110)Validated in crates/ferric-dft/tests/grid_prune_live_scf.rs.
h2-lda-opt.tomlH2 / STO-3Gksdft LDA / optimize—
water-qmmm.tomlH2O + Na⁺ (PQR) / STO-3Grhf + [qmmm]"vacuum −74.9629466809, embedded −74.9653197421, i.e. −1.489 kcal/mol from the ion at 4 A. Verified against ferric.run_rhf(point_charges=...) to all 10 digits."The vacuum number is at the PQR geometry, not the xyz. The embedded number is asserted by a test.
water-pcm.tomlH2O / STO-3Grhf + [pcm]—IEF-PCM water (solvent = "water", ε = 78.4).
water-rhf-smeared-charge.tomlH2O / STO-3Grhf + [external_potential]—One Gaussian-smeared charge (width, Bohr) and one point charge.
o2-uhf-stability-descent.tomlO2 triplet / STO-3Guhf"the descent follows the downhill eigenvector to the UHF minimum (-147.63530 Ha)"[scf] stability_descent = true.
h2-cell-rhf.tomlH2 / STO-3G, 4 Bohr cubic cellrhf + [cell], Gamma point—Periodic, dense J/K, exxdiv = "ewald".
h2-cell-kpts-rhf.tomlH2 / STO-3G, 4 Bohr cubic cellrhf + [cell], 2×2×2 k-mesh—Periodic k-point RHF, dense J/K.
h2-cell-kpts-ksdft.tomlH2 / STO-3G, 4 Bohr cubic cellksdft + [cell], 1×1×2 k-meshPBE0Periodic k-point closed-shell KS-DFT, dense J/K.
h2-cell-kpts-rhf-stress.tomlH2 / STO-3G, 4 Bohr cubic cellrhf + [cell], 1×1×2 shifted (centring = "mp") k-mesh, stress = true—Analytic k-point stress (Hartree/Bohr³ and GPa, also in the run log's run_end), dense J/K.
h2-cell-kpts-rhf-opt.tomlstretched H2 / STO-3G, 4 Å cubic cellrhf + [cell] 1×1×3 k-mesh / optimize—k-point atom relaxation at a fixed lattice, analytic k-point force, dense J/K.
h2-cell-kpts-rks-opt.tomlstretched H2 / STO-3G, 4 Å cubic cellksdft + [cell] 1×1×3 k-mesh / optimizePBE0k-point atom relaxation at a fixed lattice, analytic k-point Kohn-Sham force (global hybrid), dense J/K.
h-cell-kpts-uks.tomlH atom doublet / STO-3G, 4 Bohr cubic celluhf + [dft] + [cell], 1×1×2 k-meshPBEPeriodic k-point open-shell Kohn-Sham (UKS), dense J/K; rohf gives ROKS/ROHF at a mesh.
h2-cell-rks-opt.tomlstretched H2 / STO-3G, 4 Å cubic cellksdft PBE + [cell] / optimize—Gamma-point atom relaxation at a fixed lattice, analytic periodic force, dense J/K.

MP2 family

See The MP2 family.

FileSystem / basiskindReference in headerNotes
water-rimp2.tomlH2O / cc-pVDZrimp2—All-electron.
water-rimp2-frozen-core.tomlH2O / cc-pVDZrimp2Expected log line: "[ferric] frozen core: 1 orbital(s) frozen from [mp2] frozen_core = "auto""
water-rimp2-rijk.tomlH2O / cc-pVDZrimp2—RI-JK reference SCF ([scf] jk = "rijk", jk_aux = "def2-universal-jkfit"); the MP2 RI aux stays cc-pvdz-ri.
water-rimp2-local.tomlH2O / 6-31Grimp2 + [local]—Amplitude-threshold local MP2, eps = 1e-4. Sets reference = true, so it also computes the exact RI-MP2 and prints the local error against it (off by default).
alkane8-rimp2-local-direct.tomloctane / 6-31Grimp2 + [local]—Integral-direct local MP2 (integral_direct = true), every locality map at its default.
water-drpa.tomlH2O / 6-31Gdrpa—Exact dRPA (no [local]).
water-drpa-local.tomlH2O / 6-31Gdrpa + [local]—Amplitude-threshold dRPA, eps = 1e-4, reference = true (prints the error against the canonical plasmon dRPA). A comment shows the eps_sweep form.
water-mp3.tomlH2O / cc-pVDZmp3—
water-oo-rimp2.tomlH2O / cc-pVDZoo-rimp2—Proven (narrow) grade.
water-attmp2.tomlH2O / aug-cc-pVDZatt-rimp2—ω = 0.420 Å⁻¹.
water-attmp2-terfc.tomlH2O / aug-cc-pVDZatt-rimp2—att_operator = "terfc", att_r0 = 1.05 Å. Needs the terf tables.
alkane8-attmp2-terfc-local-direct.tomlC8H18 / 6-31Gatt-rimp2[local]Integral-direct amplitude-threshold LMP2 with a decoupled terfc seam (att_omega, r0·ω = 4). Needs the terf tables.
water-scs-mp2.tomlH2O / cc-pVDZscs-mp2—Grimme coefficients (defaults).
water-scs-mp2-2terfc.tomlH2O / cc-pVDZscs-mp2-2terfc—Thesis defaults r0 = 0.75/1.05 Å. Needs the terf tables.
water-laplace-rimp2.tomlH2O / cc-pVDZlaplace-mp2—
water-laplace-sos-mp2.tomlH2O / cc-pVDZlaplace-sos-mp2—
water-mp2v.tomlH2O / aug-cc-pVDZmp2-v—The header warns that aDZ is outside the fitted basis. Needs the terf tables.
water-rs-mp2-rpa.tomlH2O / aug-cc-pVDZrs-mp2-rpa—Smoke grade.

Coupled cluster and double hybrids

See Coupled cluster and RPA and GW § double hybrids.

FileSystem / basiskindReference in headerNotes
water-ccsd.tomlH2O / cc-pVDZccsd"PySCF CCSD run on the same density-fitted integrals gives E_corr = -0.2135061893 Ha"All electrons, aux cc-pvdz-ri.
water-linlccd.tomlH2O / 6-31Glinlccd—Exact LinLCCD(hh).
water-linlccd-local.tomlH2O / 6-31Glinlccd + [local]—Amplitude-threshold LinLCCD(hh), eps = 1e-4.
water-ccd.tomlH2O / STO-3Gccd—
water-ccsd-t.tomlH2O / STO-3Gccsd(t)—Prints the CCSD correlation energy, the (T) correction and the total.
water-wb97xlv.tomlH2O / 6-31Gwb97x-l-v—λ = 0.6, ω = 0.1 Bohr⁻¹ (published values). Proven (narrow) grade.
water-b2plyp.tomlH2O / cc-pVDZb2plyp—Spike grade. Aux cc-pvdz-rifit (an alias of cc-pvdz-ri).

RPA, C6 and properties

See RPA and GW.

FileSystem / basiskindReference in headerNotes
water-pdep-rpa.tomlH2O / cc-pVDZpdep-rpa—Writes eigenpotential cube files.
h2o-pdep-rpa-props.tomlH2O / cc-pVDZpdep-rpa—NPZ export.
benzene-pdep-rpa.tomlbenzene / def2-SVPpdep-rpa—RI-JK SCF.
benzene-pdep-rpa-export.tomlbenzene / cc-pVDZpdep-rpa—Exports cube files.
benzene-rijk-pdep-rpa.tomlbenzene / cc-pVDZpdep-rpa—Full rank (trunc_thresh = 0).
water-c6-pdep.tomlH2O / aug-cc-pVTZpdep-rpa, c6_source = "pdep""DOSD molecular reference (Meath/Toulouse): C6(H2O–H2O) = 45.3 a.u."Compare the printed "molecular C6" line against it, not the sum of the NPZ c6_iso entries. Writes to /tmp.
argon-c6-rpa-pbe.tomlAr / aug-cc-pVTZpdep-rpa @PBE"C6(Ar-Ar) = 56.4 a.u. vs DOSD 64.3 (-12%); the full He/Ne/Ar sweep gives mean |err| 8.9% at RPA@PBE — vs 39% at RPA@HF"Writes to /tmp.
water-tdhf-static-alpha.tomlH2O / cc-pVDZtdhf-static-polarizability @PBE"alpha_iso = 5.20 a.u. … against the DOSD reference 9.64 a.u.: 46% LOW"Sets [gw] scissor = 0.36 Ha; at scissor = 0 the run is refused (negative α diagonal). Static α only. Executed by a test (for the warning only).

GW and BSE

FileSystem / basiskindReference in headerNotes
water-g0w0-pbe.tomlH2O / cc-pVDZgw G0W0@PBE"HOMO IP should match … PySCF gw_ac reference (11.1714 eV) to <0.1 eV" (crates/ferric-gw/tests/g0w0_pbe_h2o.rs)
oh-ugw.tomlOH doublet / cc-pVDZgw U-G0W0@UHF"alpha-HOMO IP in the ~13-14 eV window, bracketing experiment 13.02 eV" (crates/ferric-gw/tests/oh_u_g0w0.rs)
oh-ugw-rohf.tomlOH doublet / cc-pVDZgw U-G0W0@ROHF—[gw] reference = "rohf".
water-bse-tda.tomlH2O / cc-pVDZbse-tda"Measured via this exact TOML (2026-07-18): 8.4572 eV", against a PySCF-integral cross-check of 8.46 eV and a sanity window of [5, 12] eV
water-augccpvdz-bse-tda.tomlH2O / aug-cc-pVDZbse-tdaLiterature: "aug-cc-pVDZ CCSDT Delta_Evert = 9.279 eV, f = 0.058; CBS TBE = 7.71+-0.02 eV, f = 0.052+-0.001"No ferric number is recorded.
h2co-bse-tda.tomlH2CO / cc-pVDZbse-tda—Pilot run. The lowest state is dark (f ≈ 0).
c2h4-bse-tda.tomlC2H4 / cc-pVDZbse-tda—Pilot run.
formaldehyde-bse-tda-augdz.tomlH2CO (QUESTDB geometry) / aug-cc-pVDZbse-tdaLiterature: "lowest singlet 1^1A2 (n->pi*, symmetry-forbidden) at 3.966 eV (QUESTDB TBE…)"The template for the Thiel-set files below.
acetaldehyde-bse-tda-augdz.toml, butadiene-bse-tda-augdz.toml, cyclopropene-bse-tda-augdz.toml, ethylene-bse-tda-augdz.toml, furan-bse-tda-augdz.toml, glyoxal-bse-tda-augdz.toml, pyrazine-bse-tda-augdz.toml, pyridine-bse-tda-augdz.toml, pyrimidine-bse-tda-augdz.tomlQUESTDB Thiel-set molecules / aug-cc-pVDZbse-tda—Same setup as the formaldehyde file. The reference values are in testdata/reference/thiel_set_subset.json.

TDDFT

FileSystem / basiskindReference in headerNotes
water-tda.tomlH2O / cc-pVDZtda (CIS, 5 roots)—Default aux cc-pvdz-rifit (an alias of cc-pvdz-ri).
water-tddft-pbe.tomlH2O / cc-pVDZtddft @PBE (5 roots)—Includes the f_xc kernel.

See also Capabilities and validation for the grade of each kind and its evidence, and Input reference for every key.

Run logs

Every ferric CLI run writes a machine-readable JSON Lines log, without being asked.

The format is ferric's own; it is not QCSchema, and there is no QCSchema exporter. Each record's schema field (currently 1, on run_start) versions this format.

$ ferric water-rhf.toml
[ferric] JSON run log: water-rhf.ferric.jsonl
...

Why it is on by default

A result whose run left no artifact can't be checked, and stdout captures get truncated. The run log keeps the record on disk whether or not anyone captured stdout.

Opt out explicitly if you need to:

[output]
json = false          # no log
# json = "runs/a.jsonl"  # or put it somewhere specific

or from the command line:

ferric --no-json input.toml
ferric --json runs/benzene.jsonl input.toml

The default path is the input file's name with .ferric.jsonl in place of its extension, written beside the input.

A log never fails a calculation. If the path cannot be opened or the disk fills, ferric warns once on stderr and the run continues.

Format

One JSON object per line, written and flushed as it happens rather than buffered to the end of the run. A job that is OOM-killed, hits a wall-clock limit, or is Ctrl-C'd at iteration 90 of 100 leaves those 90 iterations on disk. Only the final line may be truncated; everything before it parses.

jq -c 'select(.record=="scf_iter") | {iter, energy, dp_rms}' water-rhf.ferric.jsonl

Every record carries record (its type), seq (a gap-free counter, so a missing record is detectable) and t (seconds since the run started).

run_start

Written before any expensive work, so it survives a job killed in the first iteration. Carries the ferric version, the git SHA the binary was built from (null if the build had no checkout; a -dirty suffix means uncommitted changes were compiled in), a UTC timestamp, the resolved config (including the memory budget in effect, not the key as written) and the molecule.

scf_iter

One per SCF iteration: iter, energy, de, dp_rms, dp_max, err_max, and grad_rms where the solver forms one. rung says which ladder rung the iteration belongs to. method is rhf/uhf/rohf or their KS counterparts rks/uks/roks.

These are the quantities the solver already computes for its convergence decision (see scf_converged); the log only reports them.

dp_rms and dp_max are "inf" on the first iteration of each SCF, before there is a previous density to compare against. JSON has no infinity, so non-finite values are written as the strings "inf", "-inf" and "NaN" rather than as null, which would make a NaN indistinguishable from a field that was not reported.

guess_scf_iter

Identical shape, but for the free-atom SCFs the SAD/MINAO initial guess runs per element. They are real work and can fail, so they are logged, under a different record type so that free oxygen's energy is never mistaken for the molecule's.

ladder_rung

One per convergence-ladder rung: rung, tricks (which accelerators it enables), iterations, exit, energy, converged and max_iter.

max_iter is the rung's own cap. In the CLI, kind = "rhf" and kind = "ksdft" run through a convergence ladder, and unless you define your own [[scf.ladder]] rungs, the default ladder sets its own per-rung iteration caps:

kindrung 0rungs 1–4
rhf6060 / 60 / 80 / 100
ksdft[scf] max_iter60 / 60 / 80 / 100

So for rhf, [scf] max_iter does not set the per-rung cap. A run that reports "iterations = 100" is usually rung 4 using up its own budget, not a single hundred-iteration SCF. The cap and the count are recorded together so the log can't be misread that way.

run_end

The terminal record: energy, converged, exit, wall_s, cpu_s and peak_rss_bytes (the high-water mark, not the RSS at exit).

For a post-SCF method the energy is the SCF reference, not the method's total. extra.energy_is says which. Per-iteration records for MP2/RPA/CC/GW are not implemented yet.

Only task = "energy" and periodic task = "optimize" write run_end. A molecular task = "optimize" and task = "frequencies" currently write run_start and the SCF iteration records, but no run_end, ladder_rung or result. A periodic [cell] optimization writes run_end (see below), also when it stops at max_steps without converging; it writes none when it ends in an error. Read the final geometry and energy of an optimization from stdout. A finished task = "frequencies" writes one frequencies record instead (below). A missing run_end in such a log doesn't mean the run failed (MEASURED on examples/h2-lda-opt.toml, 2026-09-23).

Precision provenance (precision)

run_end.extra.precision records which mixed-precision kernels the whole run went through: every kernel, counted from the start of the run to run_end, whichever code called it (the SCF, the method, and MP2 variants such as SCS-MP2, SCS-MP2(2terfc), terfc att-rimp2 and the double hybrids, which carry no record of their own). run_end.extra.scf_precision is the SCF's own record. A run that writes no run_end from the molecular dispatch (open-shell uhf/rohf energies and their KS forms, task = "optimize", task = "frequencies", wb97x-l-v, b2plyp, dsd-pbep86) writes the whole-run record as a precision record instead. A rimp2 run's result.components.precision covers the MP2 correlation energy alone (closed shell and unrestricted). The Python results carry the same object as RhfResult.precision, UhfResult.precision, DftResult.precision, RiMp2Result.precision and RiMp2Result.scf_precision. An all-f64 run, which is every run that does not set [precision] mode = "mixed", says so explicitly:

{"precision": "f64", "counter_scope": "process", "kernels": []}

Otherwise kernels has one entry per kernel that was allowed to run mixed for that result, or that ran mixed:

fieldmeaning
kernelthe kernel name (rimp2-energy, xc-vxc, dfj-pack, dfk-occ, cosx-kern)
requestedmixed when [precision] allowed it, f64 when only a test seam ran it
modemixed when at least one call ran mixed, else f64 (allowed, but nothing ran mixed)
devicewhere the mixed calls ran: "cpu", or {"ordinal", "name", "cc"} of the CUDA device; null when calls_mixed is 0
calls_mixedcalls that ran mixed: one RI-MP2 energy (per spin block when unrestricted), one XC potential pass, one RI-J or DF-K build, one COSX K build with f32 units
calls_f64_fallback{"device_f64", "f32_range", "cpu_f64"}: calls, or parts of calls, that ran f64 instead (mixed kernel unavailable on the device, a value beyond the f32 range, or the whole call on the CPU)
ladderfor xc-vxc, dfj-pack and dfk-occ: {"builds_mixed", "builds_total", "exit_rebuilt_f64"} of the SCF precision ladder; null for the other kernels
error_bound{"kind": "none_computed", "why": ...} for every kernel today. No kernel computes a per-run error bound yet; the why says what exists instead (the measured error maps on the validation page). A computed bound would read {"kind": "a_posteriori", "eh", "method"}; a number is never invented

precision is mixed when any kernel's mode is mixed. The counts are process-wide counter differences over the call, which counter_scope: "process" states in every record: two calculations running at the same time in one Python process can see each other's counts; a sequential run is exact. The record is observation only: an f64 energy is bit-identical with or without it.

frequencies

Written once by a finished task = "frequencies" run, which writes no run_end or result. It carries the numbers the printout rounds, at full precision:

fieldmeaning
hessian_sourceanalytic, analytic-moving-grid or finite-difference
energyenergy at the input geometry, Hartree; with [dft] dispersion the corrected total, KS + dispersion
correction_energythe dispersion part of energy (0 without [dft] dispersion)
frequencies_cm1vibrational wavenumbers, cm⁻¹, ascending; negative = imaginary
trans_rot_frequencies_cm1the projected-out translation/rotation modes (≈ 0, a diagnostic)
asymmetrylargest |H_ij − H_ji| of the raw Hessian, Hartree/Bohr²
n_gradient_evaluationsgradients the finite-difference Hessian took (0 when analytic)
cartesian_hessianthe Cartesian Hessian before mass-weighting, row-major 3N × 3N, Hartree/Bohr²

Non-finite floats are written as the strings "NaN", "inf", "-inf", as everywhere in the log.

result

The number a correlated run was launched to produce, written once after the method finishes. Plain SCF runs (rhf, uhf, rohf, ksdft) don't write one, because for them run_end.energy is the answer.

fieldmeaning
kindthe method that produced it, e.g. "rimp2", "ccsd", "gw"
totalthe headline number a user would quote
componentsthe decomposition that makes total checkable — e_corr, e_os/e_ss, the reference energy, and whatever else the method defines

total and run_end.energy are different numbers for every post-SCF method. A kind="rimp2" run's run_end.energy is the RHF energy it was built on; its result.total is the RI-MP2 total. Reading the wrong one gives an uncorrelated answer without any error.

components exists because a total alone cannot be reconciled against a reference implementation; the pieces show where a disagreement comes from.

result_unlogged

A post-SCF method that isn't wired up to result yet writes this instead, naming the kind. The methods that do write result are rimp2, oo-rimp2, att-rimp2, laplace-mp2, laplace-sos-mp2, scs-mp2, scs-mp2-2terfc, mp3, ccsd, ccd, ccsd(t), linlccd, drpa, mp2-v and rs-mp2-rpa. For rimp2, drpa and linlccd the record's components carry "local": null for the exact method, else {"scheme", "eps", "keep_fraction", "integral_direct"}. A rimp2 record also carries "timings". A local att-rimp2 record carries both keys under kind: "att-rimp2", and an exact att-rimp2 record has neither key, so read them with a default. "timings" is null for the exact method; for a local run, the per-stage wall-clock seconds {"t_spaces_s", "t_assembly_s", "t_solve_s", "t_reference_s", "direct"}, where direct is null in core and, with integral_direct = true, every integral-direct assembly counter and timer (strip_rows_mean/_max, strip_cols_mean/_max, strip_bytes_total, n_eri3_shell_triples, n_eri3_skipped, n_metric_shell_pairs, virt_cand_mean/_max, t_maps_s, t_eri3_s, t_eri3_fill_thread_s, t_eri3_eval_thread_s, t_eri3_scatter_thread_s, t_eri3_transform_thread_s, t_eri3_setup_s, n_eri3_slabs, transform_flops, transform_flops_own_support, n_eri3_integrals, n_eri3_null_blocks, aux_major_task_bytes_max, aux_major_concurrency, aux_major_in_flight_peak, aux_major_coeff_bytes, n_batches, batch_work_max_s, batch_work_mean_s, t_metric_s, t_pairs_s, n_pair_groups, n_distinct_occ_per_group, n_trsm, n_w_reused, own_w_bytes, own_w_bytes_held, group_scratch_bytes_max, t_loc_s). The *_thread_s fields are summed over batch workers (thread-seconds), the eval/scatter split is null unless it was measured, and t_reference_s is 0 when the opt-in reference was off. t_loc_s is counted inside t_assembly_s, so do not add it to the other stages. Timings are not reproducible run to run.

Without it, a log with no result record could mean the method does not record one yet, or that the run died before producing one. With it, a missing record means the run failed.

Example

A water/STO-3G RHF run, abridged:

{"record":"run_start","schema":1,"seq":0,"t":0.002,"ferric_version":"0.1.0","git_sha":"396e0d61eded-dirty","timestamp":"2026-09-18T16:04:12Z","config":{"method":"rhf","basis":"sto-3g","max_iter":100,"density_conv":1e-7,"memory_budget_bytes":13018454425,"rayon_num_threads":12,"mpi_ranks":1},"molecule":{"formula":"H2O","n_atoms":3,"n_basis":7,"n_electrons":10,"charge":0,"multiplicity":1}}
{"record":"guess_scf_iter","seq":1,"t":0.014,"method":"uhf","iter":1,"energy":-0.4665818503784864,"dp_rms":"inf","dp_max":"inf","de":0.4665818503784864,"err_max":0.0,"grad_rms":null,"rung":0}
{"record":"scf_iter","seq":5,"t":0.017,"method":"rhf","iter":1,"energy":-74.68273456362847,"de":74.68273456362847,"dp_rms":"inf","dp_max":"inf","err_max":1.6088693243641559,"grad_rms":0.5469406324593924,"rung":0}
{"record":"scf_iter","seq":13,"t":0.019,"method":"rhf","iter":9,"energy":-74.9631468000391,"de":5.684341886080802e-14,"dp_rms":1.356320515468342e-10,"dp_max":4.726057323267696e-10,"err_max":3.5214886562329184e-14,"grad_rms":1.2777284834157483e-14,"rung":0}
{"record":"ladder_rung","seq":14,"t":0.019,"rung":0,"tricks":["diis"],"max_iter":60,"iterations":9,"exit":"Converged","energy":-74.9631468000391,"converged":true}
{"record":"run_end","seq":15,"t":0.019,"energy":-74.9631468000391,"converged":true,"exit":"Converged","wall_s":0.0189,"cpu_s":0.05,"peak_rss_bytes":128815104,"extra":{"method":"rhf","task":"energy","scf_iterations":9,"energy_is":"total","precision":{"precision":"f64","counter_scope":"process","kernels":[]}}}

Periodic k-point stress

A periodic [cell] run with kmesh and stress = true adds extra.stress_hartree_per_bohr3 (the analytic 3×3 stress tensor σ = (1/Ω) dE/dε of the converged SCF, rows and columns in the Cartesian x, y, z of the lattice frame, full f64 precision), extra.stress_gpa (the same tensor in GPa) and extra.stress_unit = "hartree_per_bohr3". run_end.energy is the total energy per cell. run_start.config.stress records the request; run_end.extra also carries stress_requested and scf_iterations. If the SCF does not converge the tensor is absent and run_end has converged = false, exit = "not_converged"; a library error closes the log with exit = "error" and extra.error.

Periodic geometry optimization

A periodic [cell] run with task = "optimize" (Gamma point or kmesh) adds extra.final_geometry_bohr (the returned Cartesian positions in Bohr, flat [x0, y0, z0, x1, ...], full f64 precision) and extra.final_gradient_hartree_per_bohr (the analytic gradient dE/dR per cell evaluated at exactly that geometry, same layout). run_end.energy is the energy per cell at that geometry. When the optimization ends in an error (a refusal or a non-converged SCF step) the run exits with the error and writes no run_end record, so neither key exists; an optimization that stops at max_steps without converging still writes both (run_end.converged = false).

Point-group-blocked runs

With [scf] symmetry = true the terminal record carries extra.symmetry = {point_group, max_symmetrization_shift_bohr, irrep_nelec, geometry}. irrep_nelec maps each irrep to its electron count in the converged state, geometry is "symmetrized", and run_end.energy is the energy of that symmetrized geometry (it differs from the input geometry's by the effect of max_symmetrization_shift_bohr, at most 1e-4 Bohr). Without the key there is no symmetry field.

Does it change the answer?

No. SCF energies are bit-identical (f64::to_bits()) with the log on and off, asserted across RHF, RKS, UHF and ROHF in crates/ferric-scf/tests/runlog_bit_identity.rs. Every value written is one the solver had already computed for its own convergence decision, and nothing is read back.

Does it cost anything?

The per-record cost has not yet been measured on a quiet machine. Run scripts/bench-runlog-overhead.sh to measure it; the script explains why the emit path must be timed directly rather than by diffing whole-process wall times, and what to compare the result against.

Einsum spec grammar

ferric contracts two tensors under a spec string such as ij,jk->ik. The same parser serves the Rust einsum! macro, ferric_tensors::einsum_dyn and the Python function ferric.einsum.

The grammar is stricter than numpy.einsum. A spec that breaks a rule is an error; there is no fallback to another algorithm.

Every example below is a test in crates/ferric-tensors/tests/einsum_spec_examples.rs with a hand-computed expected value. A further test fails if a spec tested there is missing from this page. The parser is crates/ferric-einsum-spec/src/lib.rs.

Entry points

Entry pointSpec checkedScaleScalar spec returns
einsum!(spec, &a, &b) (Rust macro)at compile time; the spec must be a string literaloptional fourth argument, default 1.0f64
einsum_dyn(spec, a, b, scale) (Rust)at run time, as TensorErrorscale: f640-dimensional ArrayD<f64>
ferric.einsum(spec, a, b, scale=1.0) (Python)at run time, as ValueErrorscale keywordPython float

The macro takes Tensor or ArrayD<f64> operands. For Tensor operands a debug build also checks that contracted and batch axes carry the same Axis label. A shape mismatch inside a macro call panics; einsum_dyn returns an error instead.

Grammar

spec   = left "," right "->" output
left, right, output = zero or more index characters
  • Spaces are removed before parsing.
  • An index is one char, and i and I are different indices. The parser splits on the first -> and on ,, so those characters cannot be indices.
  • The spec must contain -> and exactly two comma-separated inputs.
  • No index may repeat within one term, including the output. Traces and diagonals of one operand (ii,...) are rejected.
  • Each operand has as many axes as its term has indices; einsum_dyn checks this.

Index classes

Each index falls in one class by where it appears.

Appears inClassEffect
both inputs, not the outputcontractedsummed; the GEMM inner axis
both inputs and the outputbatchelement-wise; one GEMM per batch slice
one input and the outputfreekept in the output
one input onlyerrorimplicit single-operand sums are not supported

An index in the output that is in neither input is also an error.

Output order

The output lists indices in three groups: batch indices, then free indices of the left operand, then free indices of the right operand. Order within a group is free and sets the output axis order. A group that appears out of sequence is an error.

Examples

Operands used below: A = [[1,2,3],[4,5,6]] (2x3), B = [[1,0],[0,1],[1,1]] (3x2).

SpecOperationResult
ij,jk->ikmatrix product A B[[4,5],[10,11]]
ij , jk -> iksame; spaces ignored[[4,5],[10,11]]
ab,bc->acsame; letters are labels only[[4,5],[10,11]]
ij,j->iA times [1,1,1][6,15]
i,j->ijouter product of [1,2] and [3,4,5][[3,4,5],[6,8,10]]
ij,ij->full contraction A:A91
ij,ij->ijHadamard product A*A[[1,4,9],[16,25,36]]
kij,kij->ijdot over k per (i,j), k=2, shape (2,1,3)[[17,29,45]]
ijk,kl->jili,j free in operand order j,i; shape (1,2,2) with (2,1)shape (2,1,1)

Scale

The scale multiplies the whole result. With scale -0.5, ij,jk->ik gives [[-2,-2.5],[-5,-5.5]]. For a scalar spec, scale 2 gives 182.

let s: f64 = einsum!("ij,ij->", &a, &a, 2.0); // 182.0
let d = einsum_dyn("ij,ij->", a.view(), a.view(), 2.0).unwrap();
assert_eq!(s, d[IxDyn(&[])]);

Errors

Each message is asserted verbatim in malformed_specs_are_errors and operand_mismatches_are_errors_at_run_time. einsum! reports parse errors as compile errors with the same text.

SpecError
ij,jkeinsum spec must contain '->'
ij->ijeinsum spec must have exactly two comma-separated inputs
ij,jk,kl->ileinsum is binary: exactly two inputs
ii,jk->jkindex 'i' is repeated in the left operand (traces / diagonals of one operand are not supported)
ij,jk->kkindex 'k' is repeated in the output (traces / diagonals of one operand are not supported)
ij,jk->kindex 'i' appears in an input but is neither contracted nor in the output (implicit single-operand sums are not supported)
ij,jk->ikzoutput index 'z' is not a free or batch index of either input
ij,jk->kioutput indices must be listed as (batch indices, then left-operand free indices, then right-operand free indices) to match the runtime axis order

einsum_dyn adds two checks that are TensorError::Operand (Python ValueError):

  • an operand whose number of axes differs from its term;
  • a shared index whose extents differ between the operands.

An operand with a zero-extent axis returns an array of zeros with the output shape.

Execution

The contraction is one binary product, not a loop over indices.

  1. Each operand is permuted so batch, free and contracted axes are contiguous. When the needed order is the identity or the swap of the two axis groups, the operand is passed as a (transposed) view with no copy.
  2. Axes are flattened to a matrix and multiplied with ndarray::linalg::general_mat_mul. ferric-tensors builds ndarray with its blas feature through ndarray-linalg, so the product is an OpenBLAS dgemm.
  3. With batch indices, one GEMM runs per batch slice, in a sequential loop.
  4. The contracted axis is summed in blocks of GEMM_K_BLOCK (128). Large products split output rows over rayon bands; this path is bit-identical to the serial product.
  5. The scale is applied (alpha of the batched GEMM, or a pass over the result when there is no batch index) and the result is reshaped to the output shape.

An outer product (i,j->ij) is a GEMM with inner dimension 1. Contractions run on the CPU unless the crate is built with the gpu feature.

Set OPENBLAS_NUM_THREADS=1 as described in Installation.

API documentation

The crate-level API docs are generated by rustdoc and published alongside this book.

Browse the API documentation →

That link is only present when the most recent CI run on main succeeded. cargo doc does not link, but it runs build scripts, and ferric-integrals' build script compiles a C++ shim that needs libint2.hpp. The API docs are therefore built by the CI workflow, which already builds and caches libint2, and handed to the docs workflow as an artifact. If CI was red, the book still publishes and this link 404s; generate the docs locally in that case.

Generating locally

cargo doc --workspace --no-deps --open

Drop --no-deps to include dependency documentation as well (much slower, and much larger).

If that fails with "Only one may be documented at once since they output to the same path", add --exclude ferric-python. The pyo3 crate's lib is named ferric so that Python's import ferric works, and that name collides with the ferric facade crate. Excluding it loses nothing: it is a cdylib, and its surface is documented in Python bindings.

Entry points

The most useful starting points, by crate:

CrateStart at
ferric_coreMolecule, BasisSet, Shell
ferric_scfsolve_rhf, solve_uhf, solve_rohf; constrained DFT in cdft_driver::solve_cdft_uhf and cdft_coupling::coupling_hab (Python: run_cdft, cdft_coupling)
ferric_mp2ri_mp2, oo_ri_mp2
ferric_ccccsd_closed_shell, ccsd_t_closed_shell
ferric_rpapdep_polarizability_static, RPA correlation drivers
ferric_gwrun_gw, run_evgw
ferric_dftKsXc, functional construction via libxc
ferric_tensorsthe einsum! macro

On reading the docs

Doc comments record design reasoning at the point of use: convergence hazards, memory-accounting details, parallelizations that would be unsafe, and optimizations that were rejected, with the reason. They are kept next to the code rather than in prose docs, so they stay in sync with it.

References and citing

Citing ferric

If you publish a number computed with ferric, cite three things:

  1. The software. The repository carries a CITATION.cff, which GitHub turns into a "Cite this repository" entry:

    Goldey, M. ferric: a Rust-native quantum chemistry engine. https://github.com/mgoldey/ferric. Licensed MIT OR Apache-2.0.

    Give the version and commit hash you ran (ferric.build_info() reports both); the only releases so far are pre-releases (0.1.0rc*).

  2. The libraries every calculation goes through.

    • libint2 (all Gaussian integrals): E. F. Valeev, Libint: a library for the evaluation of molecular integrals of many-body operators over Gaussian functions, https://github.com/evaleev/libint. Cite the version you built against (ferric builds on libint 2.7+).
    • libxc (every DFT functional): S. Lehtola, C. Steigemann, M. J. T. Oliveira & M. A. L. Marques, SoftwareX 7, 1 (2018), doi:10.1016/j.softx.2017.11.002. Only needed if you ran a DFT functional or a DFT reference.
  3. The method papers for what you ran, from the list below. Each method page also ends with a short Cite line naming the entries it relies on.

Methods implemented in ferric

Entries are limited to methods the code implements, and most are the references the source code itself cites.

SCF, convergence and integrals

  • Szabo & Ostlund, Modern Quantum Chemistry (Dover, 1996)
  • Pulay, Chem. Phys. Lett. 73, 393 (1980): DIIS
  • Kudin, Scuseria & Cancès, J. Chem. Phys. 116, 8255 (2002): EDIIS
  • Gilbert, Besley & Gill, J. Phys. Chem. A 112, 13164 (2008): maximum overlap method (MOM)
  • Ochsenfeld, White & Head-Gordon, J. Chem. Phys. 109, 1663 (1998): LinK exchange
  • Maurer, Lambrecht & Ochsenfeld, J. Chem. Phys. 136, 144107 (2012): QQR screening
  • Thompson & Ochsenfeld, J. Chem. Phys. 147, 144101 (2017): CSB and CSAM integral screening
  • White & Head-Gordon, J. Chem. Phys. 101, 6593 (1994): continuous fast multipole method
  • Neese, Wennmohs, Hansen & Becker, Chem. Phys. 356, 98 (2009): COSX seminumerical exchange
  • Izsák & Neese, J. Chem. Phys. 135, 144105 (2011): COSX overlap fitting
  • Weigend, Phys. Chem. Chem. Phys. 4, 4285 (2002): RI-JK (fully direct RI-HF) and its auxiliary sets
  • Dunlap, J. Mol. Struct. (THEOCHEM) 529, 37 (2000): robust density fitting

DFT, grids and dispersion corrections

  • Becke, J. Chem. Phys. 88, 2547 (1988): multicentre integration (Becke partitioning)
  • Treutler & Ahlrichs, J. Chem. Phys. 102, 346 (1995): radial grids (M4 mapping)
  • Lebedev & Laikov, Dokl. Math. 59, 477 (1999): angular grids
  • Perdew, Burke & Ernzerhof, Phys. Rev. Lett. 77, 3865 (1996): PBE
  • Becke, J. Chem. Phys. 98, 5648 (1993); Stephens, Devlin, Chabalowski & Frisch, J. Phys. Chem. 98, 11623 (1994): B3LYP
  • Vydrov & Van Voorhis, J. Chem. Phys. 133, 244103 (2010): VV10 nonlocal correlation
  • Mardirossian & Head-Gordon, Phys. Chem. Chem. Phys. 16, 9904 (2014): ωB97X-V
  • Sun, Ruzsinszky & Perdew, Phys. Rev. Lett. 115, 036402 (2015): SCAN
  • Furness, Kaplan, Ning, Perdew & Sun, J. Phys. Chem. Lett. 11, 8208 (2020): r2SCAN
  • Grimme, Antony, Ehrlich & Krieg, J. Chem. Phys. 132, 154104 (2010): DFT-D3
  • Grimme, Ehrlich & Goerigk, J. Comput. Chem. 32, 1456 (2011): Becke–Johnson damping for D3
  • Tkatchenko & Scheffler, Phys. Rev. Lett. 102, 073005 (2009): TS dispersion and free-atom reference data
  • Gould & Bučko, J. Chem. Theory Comput. 12, 3603 (2016): free-atom reference data for Z = 19–54 (all but Pd)
  • Jerabek, Schwerdtfeger & Nagle, Phys. Rev. A 98, 012508 (2018): free-atom Pd polarizability (26.14 a.u., closed-shell 4d¹⁰)
  • Gobre, PhD thesis, TU Berlin (2016), Table A.1: free-atom Pd C6 (157.5 a.u., the TS value used by libMBD) and the free-atom vdW radii R_vdW (Z = 1–54) used by MBD@rsSCS
  • Tkatchenko, DiStasio, Car & Scheffler, Phys. Rev. Lett. 108, 236402 (2012): many-body dispersion (MBD)
  • Ambrosetti, Reilly, DiStasio & Tkatchenko, J. Chem. Phys. 140, 18A508 (2014): MBD@rsSCS (range-separated screening; β for PBE, PBE0, HSE06)

Solvation and embedding

  • Cancès, Mennucci & Tomasi, J. Chem. Phys. 107, 3032 (1997): IEF-PCM
  • Klamt & Schüürmann, J. Chem. Soc., Perkin Trans. 2, 799 (1993): COSMO
  • Lin & Truhlar, J. Phys. Chem. A 109, 3991 (2005): redistributed-charge QM/MM boundary schemes

Geometry, frequencies and reaction paths

  • Pulay & Fogarasi, J. Chem. Phys. 96, 2856 (1992): redundant internal coordinates
  • Banerjee, Adams, Simons & Shepard, J. Phys. Chem. 89, 52 (1985): P-RFO saddle search
  • Bofill, J. Comput. Chem. 15, 1 (1994): Hessian update for saddle searches

Charges and properties

  • Breneman & Wiberg, J. Comput. Chem. 11, 361 (1990): CHELPG
  • Bayly, Cieplak, Cornell & Kollman, J. Phys. Chem. 97, 10269 (1993): RESP
  • Foster & Boys, Rev. Mod. Phys. 32, 300 (1960): Boys localization

MP2 family

  • Weigend, Häser, Patzelt & Ahlrichs, Chem. Phys. Lett. 294, 143 (1998): RI-MP2 auxiliary basis sets
  • Grimme, J. Chem. Phys. 118, 9095 (2003): SCS-MP2
  • Jung, Lochan, Dutoi & Head-Gordon, J. Chem. Phys. 121, 9793 (2004): SOS-MP2
  • Häser & Almlöf, J. Chem. Phys. 96, 489 (1992): Laplace-transform MP2
  • Takatsuka, Ten-no & Hackbusch, J. Chem. Phys. 129, 044112 (2008): minimax Laplace quadrature
  • Lochan & Head-Gordon, J. Chem. Phys. 126, 164101 (2007): orbital-optimized (opposite-spin) MP2
  • Bozkaya, Turney, Yamaguchi, Schaefer & Sherrill, J. Chem. Phys. 135, 104103 (2011): orbital-optimized MP2 algorithm
  • Lee & Head-Gordon, J. Chem. Theory Comput. 14, 5203 (2018): κ-regularized MP2
  • Dutoi & Head-Gordon, J. Phys. Chem. A 112, 2110 (2008): the terfc attenuator
  • Goldey & Head-Gordon, J. Phys. Chem. Lett. 3, 3592 (2012): attenuated MP2 (erfc, aug-cc-pVDZ)
  • Goldey, Dutoi & Head-Gordon, Phys. Chem. Chem. Phys. 15, 15869 (2013): attenuated MP2 in aug-cc-pVTZ (terfc)
  • Goldey & Head-Gordon, J. Phys. Chem. B 118, 6519 (2014): SCS-MP2(2terfc), separate attenuation of the two spin components
  • Goldey, Belzunces & Head-Gordon, J. Chem. Theory Comput. 11, 4159 (2015): MP2-V
  • Wang, Aldossary, Shi, Liu, Li & Head-Gordon, J. Chem. Theory Comput. 19, 7577 (2023): single-threshold local MP2 (the "WSHG23" scheme in the code)

Coupled cluster

  • Scuseria, Janssen & Schaefer, J. Chem. Phys. 89, 7382 (1988): CCSD
  • Hirata, Podeszwa, Tobita & Bartlett, J. Chem. Phys. 120, 2581 (2004): spin-adapted closed-shell CCSD equations
  • Raghavachari, Trucks, Pople & Head-Gordon, Chem. Phys. Lett. 157, 479 (1989): CCSD(T)
  • Rendell, Lee & Komornicki, Chem. Phys. Lett. 178, 462 (1991): closed-shell (T) algorithm
  • Carter-Fenk, J. Phys. Chem. A 129, 7251 (2025): LinLCCD(hh)
  • Ransford & Carter-Fenk, Phys. Chem. Chem. Phys. 28, 14428 (2026): ωB97X-L-V
  • Bartlett & Musiał, Rev. Mod. Phys. 79, 291 (2007): coupled-cluster theory (review)

Double hybrids

  • Grimme, J. Chem. Phys. 124, 034108 (2006): B2PLYP
  • Kozuch & Martin, Phys. Chem. Chem. Phys. 13, 20104 (2011): DSD-PBEP86

RPA, GW and excited states

  • Wilson, Gygi & Galli, Phys. Rev. B 78, 113303 (2008): iterative dielectric eigenpotentials (PDEP)
  • Eshuis, Yarkony & Furche, J. Chem. Phys. 132, 234114 (2010): RI-RPA and its frequency quadrature
  • Kaltak, Klimeš & Kresse, J. Chem. Theory Comput. 10, 2498 (2014): minimax imaginary-frequency grids
  • Hedin, Phys. Rev. 139, A796 (1965): the GW approximation
  • van Setten et al., J. Chem. Theory Comput. 11, 5665 (2015): GW100 benchmark (the MOLGW reference values)
  • Dreuw & Head-Gordon, Chem. Rev. 105, 4009 (2005): TDDFT and CIS (review)

Constrained DFT

  • Wu & Van Voorhis, J. Chem. Phys. 125, 164105 (2006): electron-transfer couplings from constrained DFT

Background on MP2's dispersion error

Not implemented in ferric; cited on Electronic response:

  • Cybulski & Lytle, J. Chem. Phys. 127, 141102 (2007)
  • Heßelmann, J. Chem. Phys. 128, 144112 (2008)
  • Pitoňák & Heßelmann, J. Chem. Theory Comput. 6, 168 (2010)

Software dependencies

License

Dual-licensed under either

at your option.