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. Density fitting is a separate route: when density_fit=True, its RI-J or RIJCOSX kernels supersede the four-index path and the mode is ignored. Within that route, cosx=True selects COSX-K. The high-level run_job(cosx=True) convenience also enables density fitting automatically.

For a worked resource comparison, including the 170-basis-function H2O2/cc-pVQZ timing and memory measurements, see Direct SCF or in-core integrals?.

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 whose dense tensor fits comfortably

DIRECT

SCFMode.DIRECT

O(n_shells² + n_bf²)

Large systems, bounded integral memory

All molecular SCF drivers (RHF, UHF, RKS, UKS) support the mode via identical scf_mode / scf_mode_auto_threshold fields on their options struct. Range-separated RKS and UKS are a deliberate exception to the requested mode: their attenuated-exchange kernel requires the direct builder, as described below.

AUTO, the default

from vibeqc import RHFOptions, SCFMode

opts = RHFOptions()
# opts.scf_mode defaults to SCFMode.AUTO
# opts.scf_mode_auto_threshold defaults to 140 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 140) -> CONVENTIONAL, the in-core 4-index ERI tensor is built once and the per-iteration cost is a tensor contraction.

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

The threshold is a resource policy, not a physical boundary. At the default cutoff, a dense 140-function tensor is already 2.862 GiB before any other process memory. To cap that tensor below 1 GiB, use 107, the largest basis count satisfying that limit:

from vibeqc import RHFOptions

opts = RHFOptions()
opts.scf_mode_auto_threshold = 107  # switch to DIRECT above about 1 GiB

CONVENTIONAL, the in-core path

from vibeqc import RHFOptions, SCFMode

opts = RHFOptions()
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⁴:

n_bf

Full tensor size

24

2.53 MiB

95

621.42 MiB

107

1000.06 MiB

108

1.014 GiB

170

6.223 GiB

200

11.921 GiB

250

29.104 GiB

394

179.546 GiB

These are \(n_\mathrm{bf}^4\times8\) bytes for the dense tensor alone. Add Fock, density, DIIS, eigensolver, runtime, and requested-output workspaces when sizing a job. Lower the AUTO cutoff when the tensor exceeds the workflow’s budget, or set DIRECT explicitly.

DIRECT, the integral-driven path

from vibeqc import RHFOptions, SCFMode

opts = RHFOptions()
opts.scf_mode = SCFMode.DIRECT

Instead of storing the ERI tensor, the direct Fock build considers the unique shell quartets on each SCF iteration and evaluates only those that survive screening. An 8-fold-symmetric 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; lower = fewer skipped quartets

schwarz_threshold_loose

1e-7

Loose threshold for early SCF iterations (two-phase Schwarz screening)

schwarz_threshold_tighten_at

1e-3

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

incremental_fock

True

Use an Almlöf-style ΔP incremental Fock build on the direct path

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, so the converged result hits your accuracy budget.

This is the standard two-phase refinement of Schwarz screening (Häser & Ahlrichs, J. Comput. Chem. 10, 104 (1989)). 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. When the two-phase screen changes to its tight threshold, the driver also discards the incremental cache so the converged result follows the requested accuracy budget. Set incremental_fock=False for a controlled full-build comparison.

from vibeqc import RHFOptions

opts = RHFOptions()
opts.incremental_fock = True
opts.incremental_fock_reset_freq = 8

When density_fit or cosx is active

At the options-struct level, density_fit=True selects the DF J/K infrastructure and causes it to ignore scf_mode. Inside that route, cosx=True replaces fitted exchange with COSX-K. A low-level options object with cosx=True but density_fit=False does not activate COSX; set both and provide aux_basis. The high-level run_job(cosx=True) argument performs those two steps and auto-selects an auxiliary basis when possible.

Range-separated hybrids force DIRECT

RKS and UKS range-separated hybrids need an erf-attenuated exchange build. Only the direct builder currently implements that kernel, so these jobs force DIRECT when density_fit=False, even if AUTO would choose conventional or the options request CONVENTIONAL. Combining a range-separated hybrid with density_fit=True currently raises an explicit unsupported-route error because the required attenuated three-center integrals are not implemented.

Performance

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

This sweep uses def2-SVP throughout, so its H2O row has 24 basis functions. The H2O/6-31G* examples use 18 basis functions instead.

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

This historical sweep placed the speed crossover against ORCA near 200 BF on that host. The current AUTO policy switches at 140 BF because the CPU-time crossover is not a safe memory cutoff: the dense tensor is already 2.862 GiB at 140 BF. Energy agreement against ORCA was ≤ 2.4 µHa across all converged cells.

That wall-time crossover does not define a safe memory crossover. A matched one-core H2O2/cc-pVQZ case at 170 basis functions used 45.550 s wall time, 45.032 s active CPU time, and 6611.05 MiB peak RSS with vibe-qc’s conventional path. ORCA 6.1.1 used 52.669 s wall time, 51.232 s active CPU time, and 126.25 MiB peak RSS in the same allocation. The energy difference was \(1.53\times10^{-10}\) Ha. The worked tutorial derives the \(170^4\) allocation, separates the different-host direct validation, and defines a safe crossover sweep.

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 requested mode is DIRECT or when AUTO resolves to DIRECT.