SCF Fock-build modes

vibe-qc offers two Fock-build modes for molecular SCF — the strategy that constructs the J (Coulomb) and K (exchange) matrices each iteration. The mode is orthogonal to density fitting / RIJCOSX — when density_fit=True, the RI-J and COSX-K kernels supersede the four-index path and the mode is ignored.

Three modes

Mode

Enum value

Memory

Best for

AUTO

SCFMode.AUTO

Default — picks based on system size

CONVENTIONAL

SCFMode.CONVENTIONAL

O(n_bf⁴)

Small systems ((\lesssim) 150 BF)

DIRECT

SCFMode.DIRECT

O(n_shells² + n_bf²)

Large systems — no OOM wall

All molecular SCF drivers (RHF, UHF, RKS, UKS) support the mode via identical scf_mode / scf_mode_auto_threshold fields on their options struct.

AUTO — the default

from vibeqc import RHFOptions, SCFMode

opts = RHFOptions()
# opts.scf_mode defaults to SCFMode.AUTO
# opts.scf_mode_auto_threshold defaults to 200 BF

When scf_mode = AUTO (the default), vibe-qc counts the number of basis functions after the basis set is constructed and decides:

  • (n_{bf} \le 200)CONVENTIONAL — the in-core 4-index ERI tensor fits in RAM and the per-iteration cost is a single tensor contraction (cheap).

  • (n_{bf} > 200)DIRECT — the in-core tensor would be multi-GB; switch to on-the-fly Schwarz-screened libint quartet evaluation that stays O(n_shells²) in memory.

Tune the cutoff with scf_mode_auto_threshold:

opts.scf_mode_auto_threshold = 100  # switch to DIRECT earlier

CONVENTIONAL — the in-core path

opts.scf_mode = SCFMode.CONVENTIONAL

The full 4-index electron-repulsion integral tensor (μν|λσ) is computed once at the start and reused across SCF iterations. Fast for small systems, but the tensor grows as n_bf⁴:

System (def2-SVP)

n_bf

ERI tensor

H₂O

24

~17 MB

glycine

95

~4 GB

naphthalene

180

~53 GB

n-decane

250

~195 GB

n-hexadecane

394

~192 GB

The n-hexadecane cell OOMs at ~192 GB on typical hardware. For those systems, DIRECT takes over automatically — or you can set it explicitly.

DIRECT — the integral-driven path

opts.scf_mode = SCFMode.DIRECT

Instead of storing the ERI tensor, the direct Fock build evaluates every shell-quartet integral on the fly via libint each SCF iteration. An 8-fold-symmetric shell-quartet loop skips the vast majority of quartets using the strict Cauchy-Schwarz bound:

[ |\langle \mu\nu | \lambda\sigma \rangle| ;\le; Q(s_\mu, s_\nu) \cdot Q(s_\lambda, s_\sigma) ]

where (Q(s_a, s_b)) is precomputed from the diagonal shell-pair integrals at construction time and reused across iterations.

Direct SCF options

All fields live on the per-method options struct (RHFOptions, UHFOptions, RKSOptions, UKSOptions):

Field

Default

Description

schwarz_threshold

1e-10

Per-quartet skip bound; tighter = stricter screen

schwarz_threshold_loose

1e-7

Loose threshold for early SCF iterations (ORCA CheapIntThresh convention)

schwarz_threshold_tighten_at

1e-3

Gradient-norm cutoff — switch from loose to tight below this

incremental_fock

False

Enable Almlöf-style ΔP incremental Fock build (~2× faster on large systems)

incremental_fock_reset_freq

8

Full rebuild every N iterations to bound floating-point drift

Two-phase Schwarz tightening

By default, the direct Fock build starts with the looser schwarz_threshold_loose (1e-7) so the per-shell density envelope catches mid-SCF quartets aggressively. Once the SCF gradient norm drops below schwarz_threshold_tighten_at, the builder switches to the tight schwarz_threshold and discards the incremental cache — the converged result hits your accuracy budget.

This mirrors ORCA’s CheapIntThreshThresh transition. Set schwarz_threshold_loose <= schwarz_threshold to disable the coarsening and use a uniform tight threshold throughout.

Incremental ΔP Fock build

When incremental_fock = True, the builder caches the previous density D_prev and two-electron Fock matrix G_2e_prev. Each iteration computes ΔD = D − D_prev and returns:

[ G_{2e}[D] = G_{2e}[D_\text{prev}] + G_{2e}[\Delta D] ]

Because the per-pair density envelope inside the Schwarz screen sees |ΔD| rather than |D|, converged regions vanish from the work as the SCF progresses. Typical speedup on the n-hexadecane/def2-SVP scale: ~2× total SCF wall time.

A full rebuild happens every incremental_fock_reset_freq iterations (default 8) to bound floating-point drift. The intrinsic ΔP drift floor is ~1e-7 Ha — use conv_tol_energy 1e-7 with incremental Fock; tighter tolerances will stall at the drift floor.

opts.incremental_fock = True
opts.incremental_fock_reset_freq = 8
opts.conv_tol_energy = 1e-7    # match the ΔP drift floor

When density_fit or cosx is active

The scf_mode field is orthogonal to density_fit / cosx. When either is True, the DF or RIJCOSX kernels handle the J/K build and scf_mode is ignored — the path is determined by the density-fitting and COSX grid infrastructure, not the four-index mode.

Performance

On mars (i9-10885H, 4 threads), RHF direct / def2-SVP, same-box ORCA comparison:

System

n_bf

vibe-qc (s)

ORCA (s)

Ratio

H₂O

24

2.0

15.0

0.13

glycine

95

5.0

13.0

0.39

naphthalene

180

31.1

45.1

0.69

n-decane

250

226.3

67.2

3.37

n-hexadecane

394

639.2

185.3

3.45

The crossover where vibe-qc and ORCA are at parity is ~200 BF. Below that, the in-core tensor wins (and AUTO picks CONVENTIONAL); above it, the direct path carries ~3.5× overhead vs ORCA’s own direct kernel. Energy agreement vs ORCA is ≤ 2.4 µHa across all converged cells.

References

  • Almlöf, Fægri, Korsell — J. Comput. Chem. 3, 385 (1982). Principles for a direct SCF approach.

  • Häser & Ahlrichs — J. Comput. Chem. 10, 104 (1989). Cauchy-Schwarz integral screening for direct SCF.

These references are cited automatically in the .bibtex and .references output files when the job uses the DIRECT path (or when AUTO resolves to DIRECT).