Density fitting (RIJ / RIJK / RIJCOSX)¶
vibe-qc’s molecular SCF Fock build is dispatched through a
polymorphic JKBuilder interface (introduced at v0.8.0). Three
concrete kernels cover the production paths:
Kernel |
When to use |
Memory |
Wall-clock |
|---|---|---|---|
|
small systems (≤ ~250 BFs); reference / debugging |
O(N⁴) |
O(N⁴) per iter |
|
medium systems (~250-1000 BFs); hybrid DFT |
O(N²·M) |
O(N³) per iter |
|
large hybrid-DFT systems (~500-3000 BFs) |
O(N²·M) |
O(N²·M) per iter |
(N = orbital basis size; M = aux basis or grid points.)
The JKBuilder dispatch is automatic, set the right
RHFOptions flags and vibe-qc picks the kernel. The interface
is also the insertion point for the periodic-Γ JKBuilders
(see multi-k SCF) so the same
infrastructure drives molecular and periodic-Γ Fock builds.
examples/jk_builder/periodic_gamma_via_jk_builder.py
makes that sharing explicit: it drives a Γ-only periodic RHF SCF by
building Hcore/E_nuc/S from lattice-summed integrals and a
jk = vq.make_periodic_gamma_jk_builder(basis, system, lattice_opts),
then calls the same vq.run_rhf_scf_with_jk(...) C++ entry point the
molecular run_rhf wraps: DIIS, damping, level-shift, and the
quadratic fallback all come along unchanged.
All three kernels are production routes for molecular SCF. The status vocabulary used across the manual is defined in Methods: theory and status.
Theory¶
The resolution of the identity. Every two-electron integral in Hartree-Fock and hybrid DFT is a Coulomb interaction between two orbital product densities, \(\rho_{\mu\nu}(\mathbf r) = \chi_\mu(\mathbf r)\chi_\nu(\mathbf r)\), and there are \(O(N^2)\) such products for \(N\) basis functions, hence \(O(N^4)\) integrals. Density fitting expands each product in an auxiliary basis \(\{P(\mathbf r)\}\) of size \(M \approx 3N\):
Whitten (Whitten, J. L. (1973). *Coulombic potential energy integrals and approximations.* Journal of Chemical Physics, **58**(10), 4496–4501. doi:[10.1063/1.1679012](https://doi.org/10.1063/1.1679012)) and Dunlap (Dunlap, B. I., Connolly, J. W. D., and Sabin, J. R. (1979). *On some approximations in applications of Xα theory.* Journal of Chemical Physics, **71**(8), 3396–3402. doi:[10.1063/1.438728](https://doi.org/10.1063/1.438728)) showed that the right way to choose the coefficients is to minimise the Coulomb self-repulsion of the fitting error, \((\rho - \tilde\rho\,|\,\rho - \tilde\rho)\), which gives \(C^P_{\mu\nu} = \sum_Q (\mu\nu|Q)\,[V^{-1}]_{QP}\) with the Coulomb metric \(V_{PQ} = (P|Q) = \iint P(\mathbf r_1)\, r_{12}^{-1}\, Q(\mathbf r_2)\). Substituting turns the four-index tensor into a product of three-index tensors,
and it is the \(N^2 M\) tensor \(B\) that DFJKBuilder precomputes and
holds in memory (the O(N^2 M) column of the table above). This is the
“RI” of RI-J, RI-K and RI-MP2; the same \(B\) feeds the correlation
methods with a smaller -ri auxiliary basis.
Error bounds. Because the Coulomb metric is positive definite, the
fit is variational in the Coulomb energy: for a density \(\rho\) the
fitted self-repulsion obeys \((\tilde\rho|\tilde\rho) \le (\rho|\rho)\) and
the error in the Coulomb energy is second order in the fitting error,
\(E_J[\rho] - E_J[\tilde\rho] = \tfrac12 (\rho-\tilde\rho\,|\,\rho-\tilde\rho) \ge 0\).
RI-J energies therefore lie slightly above the exact ones and converge
quadratically with the quality of the auxiliary basis; with the
Eichkorn-Ahlrichs fitting sets the error is a few tens of µHa per atom
in the total energy and far less in energy differences
(Eichkorn, Karin, Treutler, Oliver, et al. (1995). *Auxiliary basis sets to approximate Coulomb potentials.* Chemical Physics Letters, **240**(4), 283–290. doi:[10.1016/0009-2614(95)00621-A](https://doi.org/10.1016/0009-2614(95)00621-A), Eichkorn, Karin, Weigend, Florian, et al. (1997). *Auxiliary basis sets for main row atoms and transition metals and their use to approximate Coulomb potentials.* Theoretical Chemistry Accounts, **97**(1–4), 119–124. doi:[10.1007/s002140050244](https://doi.org/10.1007/s002140050244)).
Exchange contracts \(B\) over the “wrong” index pair,
\(K_{\mu\nu} = \sum_P \sum_i B^P_{\mu i} B^P_{\nu i}\) with
\(B^P_{\mu i} = \sum_\lambda B^P_{\mu\lambda} C_{\lambda i}\), so the same
argument does not bound its error; that is why the JK fitting bases of
Weigend (Weigend, Florian (2002). *A fully direct RI-HF algorithm: Implementation, optimised auxiliary basis sets, demonstration of accuracy and efficiency.* Physical Chemistry Chemical Physics, **4**(18), 4285–4291. doi:[10.1039/B204199P](https://doi.org/10.1039/B204199P)) are larger than the
Coulomb-only ones and why kind="jk" and kind="ri" are different
tables below. Fitting is exact in the limit of a complete auxiliary
basis, and the coverage guard on this page exists because an auxiliary
basis with no functions on an atom is the one case where the fit is
silently and grossly wrong rather than slightly biased.
Cost. With \(B\) in hand, the Coulomb matrix costs \(O(N^2 M)\) per iteration, \(J_{\mu\nu} = \sum_P B^P_{\mu\nu} \sum_{\lambda\sigma} B^P_{\lambda\sigma} D_{\lambda\sigma}\), and exchange \(O(N^2 M\, n_{\rm occ})\) through the half-transformed \(B^P_{\mu i}\). RI-K is therefore still formally quartic, and its three-index intermediates are what fills memory first on large hybrid DFT jobs.
Chain-of-spheres exchange (COSX). Neese and coworkers (Neese, Frank, Wennmohs, Frank, et al. (2009). *Efficient, approximate and parallel Hartree-Fock and hybrid DFT calculations. A ‘chain-of-spheres’ algorithm for the Hartree-Fock exchange.* Chemical Physics, **356**(1–3), 98–109. doi:[10.1016/j.chemphys.2008.10.036](https://doi.org/10.1016/j.chemphys.2008.10.036)) replaced the fitted exchange by a seminumerical one: one of the two electron coordinates is integrated on a molecular quadrature grid \(\{\mathbf r_g, w_g\}\), the other analytically,
\(A\) is an ordinary nuclear-attraction integral with a unit charge at the
grid point, so the Coulomb singularity is handled analytically and the
grid only has to resolve the smooth orbital products. The “chain of
spheres” is the screening: around each grid point only the basis
functions whose value is non-negligible there (a sphere per shell)
enter, and only density-coupled shell pairs survive, so the work per
grid point stops growing with the molecule and the build scales as
\(O(N^2 M_{\rm grid})\) formally and close to linearly in practice. The
overlap-fitted variant of Izsák and Neese
(Izsák, Róbert and Neese, Frank (2011). *An overlap fitted chain of spheres exchange method.* Journal of Chemical Physics, **135**(14), 144105. doi:[10.1063/1.3646921](https://doi.org/10.1063/1.3646921)) replaces the grid values
\(X_{\mu g} = \chi_\mu(\mathbf r_g)\) by \(\tilde X = S Q^{-1} X\), where
\(Q_{\mu\nu} = \sum_g w_g \chi_\mu(\mathbf r_g)\chi_\nu(\mathbf r_g)\) is the
numerical overlap, so that the quadrature reproduces the analytic
overlap exactly; that is CosxVariant.FITTED, the robust default here.
The pruned multi-tier grids that vibe-qc uses for the SCF progression
are the 2021 AngularGrid family of Helmich-Paris, de Souza, Neese and
Izsák (Helmich-Paris, Benjamin, de Souza, Bernardo, et al. (2021). *An improved chain of spheres for exchange algorithm.* Journal of Chemical Physics, **155**(10), 104109. doi:[10.1063/5.0058766](https://doi.org/10.1063/5.0058766)).
Why RIJCOSX scales better than RIJK. RI-J is cheap and accurate, so both routes keep it. The difference is exchange: RI-K needs the \(N^2 M\) tensor and an \(O(N^2 M n_{\rm occ})\) contraction every iteration, while COSX needs only AO values on a grid and a screened \(O(N^2 M_{\rm grid})\) loop with a small prefactor. For hybrid DFT above roughly a thousand basis functions the exchange build is the wall-clock bottleneck, and COSX is where the order-of-magnitude savings come from (the cellulose-hexamer comparison below).
Where it fails. RI errors are systematic, not random: they cancel
in energy differences on the same auxiliary basis and reappear when two
codes use different fitting sets. COSX adds grid noise: the SCF
commutator floor sits between 1e-6 and 1e-7, so a run that demands
conv_tol_grad below that has to fall back to a denser grid, and the
COSX gradient neglects the grid-motion and weight-derivative terms
(the gradient validation numbers below quote that band explicitly).
Neither route pays off on small molecules, where the four-index build
is already fast and exact.
Quick start¶
Direct (four-index, the default)¶
from vibeqc import Atom, Molecule, run_rhf, RHFOptions
mol = Molecule([Atom(8, [0, 0, 0]),
Atom(1, [0, 1.43, -0.98]),
Atom(1, [0, -1.43, -0.98])])
result = run_rhf(mol, basis="def2-svp", options=RHFOptions())
# JKBuilder = FourIndexJKBuilder (default)
Density fitting (RIJK)¶
opts = RHFOptions(
density_fit=True,
aux_basis="def2-svp-jk", # any libint-recognised JK-fit aux
)
result = run_rhf(mol, basis="def2-svp", options=opts)
# JKBuilder = DFJKBuilder
RIJCOSX (RI-J for Coulomb + COSX for exchange)¶
from vibeqc import run_job
result = run_job(
mol,
basis="def2-svp",
method="rhf",
density_fit=True,
aux_basis="def2-svp-jk",
cosx=True, # turns the DF path into RIJCOSX
output="rijcosx_rhf",
)
# JKBuilder = COSXJKBuilder
For hybrid DFT switch to method="rks" with a functional:
result = run_job(mol, basis="def2-tzvp", method="rks", functional="b3lyp",
density_fit=True, aux_basis="def2-tzvp-jk", cosx=True,
output="rijcosx_b3lyp")
The same keywords work with method="uhf" and method="uks" (open shell). The
option objects (RHFOptions and friends) carry the same fields as attributes,
set on a default-constructed object (opts = RHFOptions(); opts.density_fit = True); they are not accepted as constructor keywords.
Auto-picking an aux basis¶
If you don’t want to remember the matching aux for each orbital basis, use the helper:
from vibeqc import default_aux_basis_for
orbital = "def2-tzvp"
aux = default_aux_basis_for(orbital, kind="jk")
# → "def2-tzvp-jk"
run_job(density_fit=True) calls this for you when aux_basis is empty,
and since v0.17.7 so do every route that reaches the bare SCF drivers from
that request: the geometry optimiser (optimize=True), the VibeQC ASE
calculator given an options struct with density_fit=True, and
compute_hessian_fd. Before that only the single point resolved the aux and
an optimisation failed with “density_fit=true requires aux_basis” (#362). An
orbital basis with no registered default (sto-3g, Pople bases, pob-*)
still raises that explicit error; pass aux_basis= there.
Recommended aux bases per orbital basis (the helper’s table):
Orbital basis |
Recommended |
Recommended |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(no matching aux yet) |
(no matching aux yet) |
Why def2-qzvp resolves to def2-qzvpp-rifit. The Basis Set
Exchange record def2-QZVP-RIFIT (version 1, data from Turbomole 7.3)
covers only 42 main-group elements – H to Ca, Ga to Sr, In to Ba and
Tl to Rn – and every 3d, 4d and 5d transition metal is absent
upstream, not lost in vibe-qc’s fetch. BSE itself describes that record
as the “RIMP2 auxiliary basis for def2-QZVPP”, and its 42 blocks are
byte-identical to the same elements of def2-qzvpp-rifit, which covers
all 72 elements of the def2 RI convention (Hättig 2005 for Li to Kr;
Hellweg, Hättig, Höfener and Klopper 2007 for Rb to Rn). Auto-resolving
def2-qzvp to the QZVPP fit therefore changes nothing for a main-group
molecule and lets a def2-QZVP transition-metal DF-MP2 / DF-CC / DLPNO
run proceed instead of being refused by the coverage guard below
(GitLab #483). aux_basis="def2-qzvp-rifit" remains selectable by
name for the 42 elements it covers.
kind="jk" aux basis = bigger, designed for the Coulomb +
exchange J/K Fock build. kind="ri" aux basis = smaller,
designed for the Coulomb-only (correlation-side) RI used in
MP2. Use the right one, JK aux on MP2 is wasteful, RI aux on
SCF is inaccurate.
pob-tzvp does not have a matching aux yet. Designing a
pob-tzvp-jk fitting basis is a separate-paper item per
docs/roadmap.md. Workaround until then:
fall back to def2-tzvp-jk (aux basis size scales with
orbital basis size, not orbital identity, so the def2 aux
works as a generic), or use density_fit=False for periodic
work.
Element coverage: the run is refused, not approximated¶
Not every shipped fitting family covers every element of the orbital basis it pairs with. The known gaps:
Auxiliary family |
Elements it does not cover |
|---|---|
|
He, Li, Be, Na, Mg; and K, Ca, Sc to Zn |
|
K, Ca (and Sc to Zn at DZ/TZ/QZ) |
|
Li, Be, Na, Mg |
|
Li, Na, K, Ca (varies by zeta) |
|
Sc to Zn, Y to Cd, La, Hf to Hg |
|
H, He, Li, Be, Na, Mg |
The def2-universal-jkfit, def2-universal-jfit and the other
def2-*-jk / def2-*-rifit families cover H to Rn.
libint2 does not raise for an element a fitting file omits: it returns an auxiliary basis with zero functions on that centre. Before v0.15.x that fit was built anyway, and the SCF converged cleanly, with a tight gradient and no warning, to an energy up to 49.6 Ha below the conventional answer (NaH/cc-pVDZ). vibe-qc now checks coverage before any fitting work and refuses the run, naming the uncovered elements and the auxiliary basis:
run_rhf: auxiliary basis 'cc-pvdz-jkfit' auto-selected for orbital
basis 'cc-pvdz' has no functions on 1 atom of this molecule:
Na (atom 0).
The refusal is deliberate: substituting a covering auxiliary
would change the fit under a caller who asked for a specific one.
To proceed, name a covering auxiliary explicitly
(aux_basis="def2-universal-jkfit" for the JK/SCF route,
aux_basis="def2-tzvpp-rifit" for the RI/correlation route),
switch to a def2 orbital basis, or run with density_fit=False.
On LiH, BeH₂, NaH and MgH₂ the def2-universal-jkfit control
reproduces the conventional SCF energy to ~1e-5 Ha, so the
density-fitting machinery itself was never the problem; only the
element coverage was.
The same check guards the post-SCF RI route, which the DLPNO
methods and run_rohf_mp2 reach with no density_fit flag at
all, and the periodic GDF auxiliary constructor.
RIJCOSX: chain-of-spheres exchange¶
RIJCOSX (Neese, Chem. Phys. 356, 98, 2009; Bykov, Petrenko, Izsák, Kossmann, Becker, Valeev, Neese, Mol. Phys. 113, 1961, 2015) replaces the O(N⁴) ERI assembly for the exchange K matrix with an O(N²·M) seminumerical chain-of- spheres algorithm:
K_{μν} ≈ Σ_g w_g χ_μ(r_g) Σ_ρ A_{νρ}(r_g) Σ_λ D_{λρ} χ_λ(r_g)
where A_{νρ}(r_g) = ∫dr' χ_ν(r') χ_ρ(r') / |r' − r_g| is
the analytic 1/|r-r’| integral with a unit charge at grid
point r_g. RI-J is used for the Coulomb piece; the same
B-tensor that the DFJKBuilder precomputes is reused.
Default COSX grid¶
n_radial = 35 (Treutler-Ahlrichs M4)
n_theta = 9 (Gauss-Legendre)
n_phi = 18 (uniform φ)
= ~5670 angular pts/atom before Becke pruning, which is
~1/8 the XC integration grid (75 × 17 × 36 = ~46 000
pts/atom). Substantially sparser is fine because COSX needs
only the smooth 1/|r−r’| kernel through χ_μ(r), no XC
gradient response.
This default tier matches ORCA’s default GridX. Override
via RHFOptions.cosx_grid:
from vibeqc import GridOptions
opts = RHFOptions(
density_fit=True, aux_basis="def2-tzvp-jk",
cosx=True,
cosx_grid=GridOptions(n_radial=50, n_theta=11, n_phi=23),
# finer than default
)
Variant + grid-level selection¶
Beyond the grid itself, RIJCOSX exposes three controls that let
you trade COSX accuracy against cost without hand-tuning the
grid. They live on every SCF Options struct (RHFOptions,
UHFOptions, RKSOptions, UKSOptions) and on
GradientOptions, and are consulted only when cosx=True.
Field |
Values |
Meaning |
|---|---|---|
|
|
K build: STANDARD = Neese 2009 seminumerical K, no overlap fit; FITTED = overlap-fitted K (global Q-junction). |
|
|
COSX grid tier: |
|
float, default |
COSX accuracy target that drives |
AUTO always resolves to FITTED, the robust overlap-fitted build.
The standard (no-fit) build is accurate per grid point on a GridX tier
(DZ gradient ~4e-5 Ha/bohr vs the legacy grid’s ~1.5e-3) but it is not
robust enough to auto-select: as an auto default it caused RIJCOSX SCF
convergence failures (open-shell especially), so it stays an explicit
opt-in (cosx_variant=STANDARD) for callers who want its speed and
accept the convergence risk. Both variants apply the one-centre analytic
correction.
When you opt into a GridX tier (cosx_grid_level 1 to 4, or -1), the auto
mapping is DZ/TZ→GridX2, QZ→GridX3 (GridX1 is the explicit quick-SP tier
only, too coarse to auto-select). Note the GridX2 caveat below.
from vibeqc import CosxVariant
opts = RHFOptions(
density_fit=True, aux_basis="def2-qzvp-jk",
cosx=True,
cosx_variant=CosxVariant.FITTED, # or AUTO (default)
cosx_grid_level=3, # GridX3, recommended for QZ
)
The GridX1-4 tiers are the 2021 5-region pruned-Lebedev “AngularGrid” grids of Helmich-Paris, de Souza, Neese & Izsák (J. Chem. Phys. 155, 104109, 2021), Table I. Each tier splits every atom’s radial shells into 5 bands with a distinct Lebedev order per band (sparse core, dense valence, thinned tail). The Table I angular point counts are exact; the radial counts and the region boundaries (taken as shell-index fractions) are documented approximations, because ORCA’s per-element Clementi-radius multipliers and differential-evolution radial parameters are not openly published (ORCA-binary only). The tiers map to ORCA’s DefGrid levels: GridX2 = DefGrid2 (ORCA’s default), GridX3 = DefGrid3, etc.
Multi-stage SCF grid progression. When you opt into a GridX tier, the
SCF does not use one fixed grid; it steps through the DefGrid stage triple
(coarse to middle to fine) following the published scheme (Helmich-Paris,
de Souza, Neese & Izsák, J. Chem. Phys. 155, 104109 (2021), Sec.
IV.A): the coarse stage converges loosely, the middle stage converges to
your SCF tolerance, and the fine (final) grid is used for exactly one
post-convergence exchange rebuild that yields the reported energy: the
SCF never iterates on the fine grid. (If a degenerate open-shell system
hits a commutator floor on the middle grid above your tolerance, the SCF
automatically falls back to converging on the fine grid instead.) The SCF
trace in the .out/.perf shows all stages concatenated; the last
iteration is the final-grid recompute, whose commutator reflects the
inter-grid exchange difference rather than SCF error.
vibeqc.cosx_grid_stages_for_level(level) returns the stage grids;
vibeqc.cosx_grid_options_for_level(level) returns the fine (last) grid
on which the reported exchange energy is built.
Reach across methods. RIJCOSX accelerates the mean-field exchange
build, so it applies to RHF, UHF, RKS, UKS (C++ drivers, with the
multi-stage progression) and to ROHF and ROKS (ROHFOptions(cosx=True, cosx_grid_level=...) / ROKSOptions, single-grid; run_job(cosx=True)
sets them for method="rohf" / "roks" as it does for the C++ drivers,
#361). Every post-HF method that builds on a
mean-field reference (MP2, RI-MP2, DLPNO-MP2, CCSD, CCSD(T), DLPNO-CC,
TDDFT) inherits RIJCOSX automatically through its reference SCF: set
cosx=True on the reference’s options (rhf_options / uhf_options /
rohf_options) and the correlated step runs on the RIJCOSX MOs. There are
no cosx fields on the MP2/CCSD option structs because COSX is purely an
SCF concern. The CAS family (CASCI/CASSCF/NEVPT2/CASPT2) is the exception:
it needs exact MO integrals, so its reference is built on the exact/DF path
regardless of cosx.
Note
Default (since 2026-06-26): cosx_grid_level = -1 (AUTO). When
cosx=True, the SCF energy now uses a 2021 GridX tier with the multi-stage
progression by default (DZ/TZ to GridX2, QZ to GridX3): more accurate than
the old legacy grid, and faster. The selection is conv-tolerance-aware: if
you tighten conv_tol_grad below 1e-6 (the seminumerical commutator-noise
floor sits between 1e-6 and 1e-7), the SCF automatically falls back to the
denser legacy grid, which can reach a tight gradient. So tight-SCF runs and
conv_tol_grad <= 1e-7 open-shell runs keep their old behaviour
automatically; you do not need to set anything.
cosx_grid_level = 0 forces the legacy grid; 1..4 force a GridX tier at
any tolerance. Gradients (GradientOptions, which has no
conv_tol_grad) still default to the legacy grid; the ASE calculator’s
force path mirrors the SCF’s resolved grid so geometry optimisations stay
energy/gradient consistent, but a hand-built GradientOptions for a GridX
optimisation should set cosx_grid_level to match the SCF.
Validation: glycine / def2-TZVP¶
The authoritative numbers are kept in Features; this table repeats them.
Quantity |
Result |
|---|---|
RHF, 4-index versus RIJK (def2-tzvp-jk) |
0.001 mHa (the RI-J/K fitting error) |
RIJCOSX SCF energy versus ORCA 6.1.1 RIJCOSX |
0.26 mHa |
RIJCOSX analytic gradient versus finite differences of the RIJCOSX energy |
agrees up to the neglected grid-motion band: ~2e-4 Ha/bohr on H2O/def2-TZVP, ~3e-3 worst case on OH/def2-SVP ( |
RIJCOSX analytic gradient versus ORCA 6.1.1 EnGrad |
|grad| 0.08696 versus 0.08703 Ha/bohr; componentwise RMS 3.3e-3, max 1.1e-2 Ha/bohr |
An earlier revision of this table claimed 0.13 mHa for the energy and
“0.13 mHa/bohr” for the gradient on the strength of the energy
comparison alone; the gradient claim was unproven and, before
2026-07-29, wrong. The present gradient residual against ORCA is the
documented fitted-Fock response term plus the neglected grid-motion and
weight derivatives (see the compute_cosx_k_gradient_contribution
notes in cpp/include/vibeqc/cosx.hpp); the gradient is consistent
with vibe-qc’s own RIJCOSX energy surface to the band quoted above,
which is what a geometry optimisation needs.
The energy agreement is within the “sub-mHa” target Neese 2009 set for COSX. For larger systems (e.g. cellulose hexamer / def2-TZVP), the COSX path becomes ~10× faster than 4-index for hybrid DFT SCF + analytic gradient combined.
One-centre exact-exchange correction (v0.8.0)¶
The COSX grid under-samples the 1/r₁₂ Coulomb singularity for shell quartets where all four basis-function centres lie on the same atom. At v0.8.0, vibe-qc applies a one-centre correction that replaces the COSX-approximate intra-atom K-matrix blocks with exact four-index ERI contractions:
K_corrected[μ∈A, ν∈A] = K_exact_1c[μ,ν]
+ (K_cosx_full − K_cosx_1c)[μ∈A, ν∈A]
The exact one-centre ERIs are precomputed at JKBuilder construction (cost: one libint quartet evaluation per intra-atom shell quartet, negligible). The COSX-approximate one-centre contribution K_cosx_1c is computed from the same GridBatches chi cache and the in-tree nuclear-attraction kernel used by the main COSX-K path, giving bit-identical AO values.
The correction is applied once post-SCF-convergence in all
four molecular SCF drivers, so it does not perturb DIIS or
SOSCF acceleration during iterations. It is always active
when cosx=True; there is no user-facing toggle.
For light atoms (H, C, N, O), the correction is sub-μHa because the COSX grid already samples intra-atom shell pairs adequately. For heavier atoms (3d / 4d transition metals) with contracted core shells, the improvement can reach ~10⁻⁴ Ha per atom in the total energy.
What improved between v0.7.x and v0.8.0¶
RIJCOSX got ~3.65× faster on the n-hexadecane / B3LYP /
def2-svp / 5-iter anchor between the v0.7.x baseline and the
v0.8.0 tip (matched-load median, M5Max, 10 perf cores: ~603 s →
~165 s wall). Energy bit-identical end-to-end (-416.178230 Ha
at iter 5). Nothing in the user-facing API changes, all the
improvements are automatic when cosx=True is set. The
contributing landings, in chronological order:
Drop dense per-grid-point A_g, build F_g block-sparse on the fly instead of materialising the full n_bf × n_bf analytical-integral matrix per point.
OMP
schedule(guided)for the per-grid-point loop, absorbs the per-point cost variance (chi-magnitude / shell- pair-survival distribution swings ~100× between nuclear and outer-grid regions). ~15-17 % wall.Per-shell radial cutoff (Schwarz × distance × dχ) on the COSX-K pair loop, drops pair-loop iteration count by 5-50× on extended systems. ~11 % wall on n-hex.
Reusable per-batch grid + chi-cache infrastructure (
GridBatch/GridBatches), basis-only AO cache evaluated once at SCF setup instead of per K-build.Per-batch L1 (primary-shell) hierarchy,
COSXJKBuilderconsumes the chi cache on everybuild_K(D)call. L1-only first landing.Per-batch L2 (density-coupled) shell screen, drops pairs where both shells fall outside the batch’s density- coupled set (Burow & Sierka 2011 § 2; Stratmann-Scuseria- Frisch 1996 § 11). The headline structural win at ~20 % wall over the pre-batch baseline.
The full per-commit measurement table + references lives in
CHANGELOG.md under [Unreleased] for v0.8.0.
DF analytic gradient status¶
The historical ~115 mHa/bohr density_fit=True gradient bug on
glycine / def2-TZVP / RHF closed in v0.8.0: the root cause was a
libint engine-state leak in compute_3c_eri_gradient_weighted
(stale derivative-buffer state between inner ket-shell calls).
After the fix the 3c-kernel residual vs finite-difference is
~1e-7 Ha / bohr (the FD truncation floor); the regression guard
is
tests/test_df_gradient.py::test_df_rhf_gradient_hcooh_def2_tzvp_matches_direct.
RIJCOSX gradients are independent of this fix and were
unaffected throughout.
One RIJCOSX-specific gap closed in the 2026-05-18 audit
remediation: the unrestricted (UHF / UKS) DF gradient silently
ignored cosx=True and returned the plain RI-JK gradient. K now
routes through the chain-of-spheres kernel per spin
(alpha_kernel = 2 alpha_HF); regression-pinned in
tests/test_rijcosx.py. The ASE calculator and the all-electron FD Hessian
also copy density_fit / aux_basis / cosx off the SCF options onto the
gradient call, so forces differentiate the same JK Hamiltonian the SCF solved.
The ASE force path additionally copies exact XML ECP fields. Molecular ECP FD
Hessians remain unsupported and fail closed before any displacement.
A separate direct-gradient bug on f-shells with two or more
different second-row elements (CO / CH₂O / glycine + def2-TZVP)
is still open for the UHF / UKS open-shell path. Closed-shell
direct gradients now auto-route through the DF gradient path
when the basis contains f-functions (max l >= 3), fully
thread-deterministic and cross-platform reproducible; the
open-shell auto-route is queued. Fall back to FD on the total
energy for open-shell f-shell systems, or restrict to def2-SVP /
single heavy-atom-type systems. Tracked in
tests/test_gradient_f_bug.py.
When to use which kernel¶
Decision tree:
System ≤ 250 BFs:
FourIndexJKBuilder(the default). Simpler, no aux-basis dependency, exactly as accurate as the 4-index ERI. No DF or COSX overhead worth introducing.System ~250-1000 BFs, hybrid DFT:
DFJKBuilder(RIJK). RIJCOSX is overkill at this size.System > 1000 BFs, hybrid DFT:
COSXJKBuilder(RIJCOSX). The exchange-K piece is where the wall-clock savings come from.System > 1000 BFs, pure GGA DFT (no exact exchange):
DFJKBuilderwithcosx=False. Skipping COSX is fine because there’s no exchange to assemble.Need analytic gradient AND > 50 BFs def2-TZVP-class: any of RIJCOSX / direct / RIJK works (the historical RIJK glycine bug closed in v0.8.0). On f-shell basis sets, closed-shell direct gradients auto-route through the DF gradient path for cross-platform reproducibility; open-shell UHF / UKS gradients on f-shells should still fall back to FD.
API surface¶
# Python-side flags on RHFOptions / UHFOptions / RKSOptions / UKSOptions:
opts = RHFOptions() # likewise UHFOptions / RKSOptions / UKSOptions
opts.density_fit = True # → DFJKBuilder
opts.aux_basis = "def2-tzvp-jk"
opts.cosx = True # → COSXJKBuilder (paired with density_fit)
opts.cosx_grid_level = -1 # GridX tier; -1 auto-selects by basis
# opts.cosx_grid is the GridOptions used when cosx_grid_level is 0
// C++-side factory (cpp/include/vibeqc/jk_builder.hpp):
std::unique_ptr<JKBuilder> make_four_index_jk_builder(const BasisSet& basis);
std::unique_ptr<JKBuilder> make_df_jk_builder(const BasisSet& basis,
const BasisSet& aux);
std::unique_ptr<JKBuilder> make_cosx_jk_builder(const BasisSet& basis,
const BasisSet& aux,
Grid cosx_grid);
The strategy interface:
class JKBuilder {
public:
virtual Eigen::MatrixXd build_J(const Eigen::MatrixXd& D) const = 0;
virtual Eigen::MatrixXd build_K(const Eigen::MatrixXd& D) const = 0;
virtual Eigen::MatrixXd build_g_rhf(const Eigen::MatrixXd& D,
double alpha_hf = 1.0) const;
};
build_g_rhf defaults to J − ½·α·K; concrete kernels override
when the J/K can fuse work (DF amortises the B-tensor
contraction).
Citations¶
Cite for any published RIJ / RIJK / RIJCOSX work (the .bibtex
sidecar of a run_job call assembles the route’s entries for you):
RI / DF in Hartree-Fock: Whitten, J. L. (1973). *Coulombic potential energy integrals and approximations.* Journal of Chemical Physics, **58**(10), 4496–4501. doi:[10.1063/1.1679012](https://doi.org/10.1063/1.1679012); Dunlap, B. I., Connolly, J. W. D., and Sabin, J. R. (1979). *On some approximations in applications of Xα theory.* Journal of Chemical Physics, **71**(8), 3396–3402. doi:[10.1063/1.438728](https://doi.org/10.1063/1.438728); Eichkorn, Karin, Treutler, Oliver, et al. (1995). *Auxiliary basis sets to approximate Coulomb potentials.* Chemical Physics Letters, **240**(4), 283–290. doi:[10.1016/0009-2614(95)00621-A](https://doi.org/10.1016/0009-2614(95)00621-A); Eichkorn, Karin, Weigend, Florian, et al. (1997). *Auxiliary basis sets for main row atoms and transition metals and their use to approximate Coulomb potentials.* Theoretical Chemistry Accounts, **97**(1–4), 119–124. doi:[10.1007/s002140050244](https://doi.org/10.1007/s002140050244).
RI-JK exchange fitting and the JK auxiliary bases: Weigend, Florian (2002). *A fully direct RI-HF algorithm: Implementation, optimised auxiliary basis sets, demonstration of accuracy and efficiency.* Physical Chemistry Chemical Physics, **4**(18), 4285–4291. doi:[10.1039/B204199P](https://doi.org/10.1039/B204199P); the def2 JK sets are Weigend, Phys. Chem. Chem. Phys. 8, 1057 (2006).
RI auxiliary bases (def2 family): Hellweg, Hättig, Höfener, Klopper, Theor. Chem. Acc. 117, 587 (2007).
COSX: Neese, Frank, Wennmohs, Frank, et al. (2009). *Efficient, approximate and parallel Hartree-Fock and hybrid DFT calculations. A ‘chain-of-spheres’ algorithm for the Hartree-Fock exchange.* Chemical Physics, **356**(1–3), 98–109. doi:[10.1016/j.chemphys.2008.10.036](https://doi.org/10.1016/j.chemphys.2008.10.036); overlap-fitted COSX: Izsák, Róbert and Neese, Frank (2011). *An overlap fitted chain of spheres exchange method.* Journal of Chemical Physics, **135**(14), 144105. doi:[10.1063/1.3646921](https://doi.org/10.1063/1.3646921); the GridX tiers: Helmich-Paris, Benjamin, de Souza, Bernardo, et al. (2021). *An improved chain of spheres for exchange algorithm.* Journal of Chemical Physics, **155**(10), 104109. doi:[10.1063/5.0058766](https://doi.org/10.1063/5.0058766).
COSX-K analytic gradient: Bykov, Petrenko, Izsák, Kossmann, Becker, Valeev, Neese, Mol. Phys. 113, 1961 (2015).
Plus your basis-set + functional citations per the rest of
docs/license.md.
See also¶
periodic_methods.md: comparative tour of vibe-qc’s periodic-SCF kernels (BIPOLE, native GDF, GPW/GAPW) and the surface-reactions workflow walkthrough.functionals.md, XC functional library (B3LYP-VWN5 sidebar, PW1PW, hybrid functional aliases).multi_k_scf.md, periodic-Γ JKBuildermulti-k SCF.
scf_convergence.md, DIIS / EDIIS+DIIS / level-shift / quadratic-fallback that the JKBuilder dispatch lands inside.
Evaluated states from the native Gamma RHF/RKS API¶
run_rhf_periodic_gamma_gdf returns result.fock_build for bare-fit RHF/RKS in
3D molecular-limit cells whose integral cutoff contains only the home cell,
with no active symmetry operations, stabilization or Fock reduction.
The builder owns the fitted tensor and one-electron matrices. Its receipt
records the actual auxiliary shells, fitting threshold, modrho setting and
resolved integral/nuclear options. Existing libint fitting is unchanged.
RKS adds the joint libxc energy and potential for the evaluated density. Its receipt records the actual functional, grid points/weights, lattice options, AO shells and XC density convention. Changes to live XC inputs require a new builder; completed states own their values. The XC energy remains an explicit component and is never inferred from half a density-potential trace. PATOM uses a separate full-HF build and eigensolver history, including for pure DFT. Pure functionals omit the fitted exchange contraction during KS builds; global hybrids retain the functional’s exchange fraction.
The returned orbitals and occupations define result.density, including when
SCF reaches its iteration limit. The physical Fock and energy are evaluated
on that density. Orbital energies are its physical Rayleigh quotients;
next_density records the further unaccelerated orbital/occupation update.
Convergence checks this density closure as well as energy and stationarity.
Legacy arrays remain writable, separate from the immutable build.
At finite temperature, entropy belongs to the returned occupations and
free_energy = energy - smearing_temperature * entropy. The SCF trace records
free energy; the FockBuild and QVF energy record the physical internal energy.
Dense and Davidson solves both request the complete retained spectrum for
Fermi occupations. Positive max_iter is required on this migrated route.
This state migration does not change fitting accuracy or admit additional methods. External XC providers, RSGDF, private slab and torus routes, symmetry-projected sources and multi-k GDF retain their existing behavior and do not yet provide this FockBuild result. Native bounded MANTA sources and BIPOLE domains remain separately qualified.