Two-electron integrals & J/K strategies#
The single biggest performance lever in a quantum-chemistry calculation is how the two-electron integrals
are handled. There are \(\mathcal{O}(N^4)\) of them, and every SCF cycle contracts them into the Coulomb
(J) and exchange (K) matrices. qc-rs exposes the choice through one keyword, ints(eri=...), and
the choice interacts with every parallelism level from the last three chapters. This chapter maps the options
so you can pick the right one.
Theory: why integral handling dominates#
For \(N\) basis functions there are formally \(N^4/8\) unique two-electron integrals \((\mu\nu|\lambda\sigma)\). At \(N = 500\) that is ~8 billion numbers — often too many to store, and expensive to recompute. Every method that follows (SCF, MP2, …) is bottlenecked here, so the strategy question is really: store them, recompute them, put them on disk, or approximate them? Each answer trades memory against CPU time differently, which is why there is no single best choice — it depends on your molecule and your hardware.
The unified eri= axis#
All of it is one keyword on ints(...), in two families.
The 4-center family — exact integrals#
These use the true \((\mu\nu|\lambda\sigma)\) integrals (no approximation); they give bit-identical energies and differ only in where the integrals live:
|
strategy |
best for |
|---|---|---|
|
walks a memory ladder: incore → ramdisk → disk → direct |
just works |
|
store all integrals in RAM |
small/medium, plenty of RAM (fastest) |
|
recompute integrals every cycle, store nothing |
large, RAM-limited |
|
spool integrals to disk once, stream each cycle |
out-of-core, a fast scratch disk |
|
the sparse disk encoding kept in RAM |
large/sparse, distributes across MPI ranks |
4c-auto is the safe default — it picks the most memory-hungry option that fits and falls back gracefully. It
never selects RI (below); RI is only ever chosen explicitly.
The RI (density-fitting) family — approximate, much cheaper#
RI (resolution of identity, from the post-SCF chapter) factors the four-index integrals through an auxiliary basis, collapsing the cost dramatically at the price of a tiny, controlled fitting error. Two sub-families differ in whether the fitted factor is stored or recomputed:
|
strategy |
note |
|---|---|---|
|
build the whitened RI factor once, keep it in RAM (μ-distributed under MPI) |
incore-speed, recompute-memory — the store- |
|
recompute the 3-center integrals every cycle, store no factor |
the memory-frugal RI |
|
recompute + spool the smaller occupied factor to disk |
out-of-core RI |
The GPU members (ri-cuda, ri-recomp-cuda, ri-ram-cuda) live in the GPU chapter. RI needs
an auxiliary basis; with no rijk= given, qc-rs auto-derives a default JK-fit aux from your orbital basis.
Exact vs RI: the fitting error, verified#
The 4-center strategies are exact and agree to the last digit; the RI strategies carry a small fitting error but are far cheaper. For water/cc-pVDZ (RHF):
import qc
water = "O 0 0 0.117; H 0 0.757 -0.469; H 0 -0.757 -0.469"
for eri in ("4c-incore", "4c-direct", "ri-ram", "ri-recomp"):
e = qc.chk.new(atom=water, ao="cc-pvdz", unit="angstrom",
rijk="cc-pvdz-jkfit").ints(eri=eri).scf(ref="r").run().scf.energy
print(f"{eri:11} {e:.6f}")
# 4c-incore -76.026794 ← exact
# 4c-direct -76.026794 ← exact (bit-identical to incore)
# ri-ram -76.026773 ← RI: +2.1e-5 fitting error
# ri-recomp -76.026773 ← RI: identical to ri-ram
The two 4-center strategies are bit-identical (−76.026794); the two RI strategies are identical to each other (−76.026773) and differ from exact by only 2.1 × 10⁻⁵ Ha — the RI fitting error, far below chemical accuracy. So RI buys a large speed/memory win for a negligible, controlled error.
Choosing a strategy#
A practical decision guide:
Default / unsure →
4c-auto. It picks well and falls back if RAM is tight.Small–medium, plenty of RAM, want exact and fastest →
4c-incore.Large, RAM-limited, want exact →
4c-direct(recompute) or4c-disk(out-of-core).Large, want the big speed/memory win and can accept ~1e-5 Ha → the RI family (
ri-ramif the factor fits in RAM,ri-recompif not). This is the usual choice at scale, and the only path for RI-MP2.GPU available →
4c-cuda/ri-cuda.
RI also composes with MPI to break the one-node memory wall (ri-ram distributes the factor by AO index, so
per-rank memory ≈ total / nranks, MPI chapter).
Worked example#
import qc
water = "O 0 0 0.117; H 0 0.757 -0.469; H 0 -0.757 -0.469"
exact = qc.chk.new(atom=water, ao="cc-pvdz", unit="angstrom").ints(eri="4c-incore").scf(ref="r").run()
ri = qc.chk.new(atom=water, ao="cc-pvdz", unit="angstrom",
rijk="cc-pvdz-jkfit").ints(eri="ri-ram").scf(ref="r").run()
print(f"exact (4c) : {exact.scf.energy:.6f}") # -76.026794
print(f"RI : {ri.scf.energy:.6f}") # -76.026773
print(f"fitting err: {abs(exact.scf.energy - ri.scf.energy):.2e} Ha") # 2.1e-05
Exercise 20
Your cc-pVTZ calculation on a big molecule dies with an out-of-memory error using
4c-incore. Give two differenteri=strategies that would let it run, and the trade-off of each.Why do
ri-ramandri-recompgive the same energy as each other but a slightly different energy from4c-incore?You want to run RI-MP2. Which
eri=family is mandatory, and which extra basis keyword must you set?
Solution to Exercise 20
(a)
4c-direct— recompute the integrals each cycle (exact, no storage, but more CPU per cycle); (b)ri-ramorri-recomp— density-fit (much less memory and CPU, at a ~1e-5 Ha fitting error).4c-diskis a third option if you have fast scratch disk.Both are RI (density-fitting) — they use the same auxiliary-basis approximation, so they share the same fitting error; they differ only in whether the fitted factor is stored or recomputed, which does not change the result.
4c-incoreuses the exact integrals, so it lacks that fitting error.The RI family is mandatory (RI-MP2 has no non-RI path). You must set the
ric=correlation-fitting auxiliary basis (e.g.ric="cc-pvdz-ri/mp2fit"), distinct from the JK-fitrijk=.
That completes Part IV. You can now scale a qc-rs calculation across CPU threads, MPI ranks on a cluster, and an NVIDIA GPU, and choose the integral strategy that fits your molecule and hardware. From here, the Reference sections give the precise details of every input and option.