This site is version 0.0.1. Most of the code on this site is drafted by an agent and needs review. I apologize for the AI-speak, we are working on rewriting it!
viv-lang.org

Notebooks · lib/hf/md_eri

Electron repulsion integrals by McMurchie-Davidson, one shell quartet at a time

The two-electron integral over four Cartesian Gaussians is, after the Hermite expansion of both products (see lib/hf/hermite),

(ab|cd) = 2 pi^(5/2) / (p q sqrt(p + q))
          * sum_h  E_ab(h)  sum_h'  (-1)^(t'+u'+v') E_cd(h')  R(h + h')

where h = (t, u, v) runs over the Hermite functions of the bra pair, h' over those of the ket pair, and R is taken at P - Q with exponent alpha = pq / (p + q). The sign comes from moving the derivatives of the ket's Hermite Gaussians over to the bra's: d/dQ = -d/dP on a function of P - Q.

Written as two matrix products. For one shell pair, stack the E values of every primitive pair side by side: E_ab is [components, primitive pairs x Hermite functions], with the contraction coefficients, the component factors and the Gaussian product prefactor folded in. For one shell quartet, the signed R values for a batch of bra primitive pairs against every ket primitive pair form a matrix BR, with a row per ket (primitive pair, Hermite function) and a column per bra (primitive pair, Hermite function). Then

Z_b = E_cd @ BR_b        [ket components, bra primitive-Hermite]
(ab|cd) = E_ab @ Z^T     [bra components, ket components]

where Z^T stacks the Z_b^T of every batch, and both sums over primitives happen inside the products. The batch is as many bra primitive pairs as keep BR near MDERI_BATCH = 4096 entries: all 36 at once for a contracted (ss|ss) quartet of oxygen 1s shells, one at a time for a (dd|dd) quartet with 10 primitives per shell, where a single BR over both primitive lists would be 3500 x 3500. Each primitive quartet costs one Boys evaluation and one R table, shared by every component, and the component work goes to @, which skips the zeros of E (an entry is zero whenever t passes i + j on its axis) and runs the same sum in the same order in both engines.

The shell-pair list is built once per basis: for every pair A >= B, the primitive-pair exponents p, centers P and the stacked E_ab. All of it lives in one flat array (mderi_store), because Viv has no records.

Schwarz screening. (ab|cd)^2 <= (ab|ab)(cd|cd), so with Q_AB = sqrt(max (ab|ab)) over the components of a shell pair, a quartet with Q_AB Q_CD below the threshold contributes less than the threshold to every integral it holds. mderi_screened skips those quartets (threshold MDERI_SCREEN_TOL = 10−14), and mderi_full computes every one; the two agree to the threshold.

One flat quartet list. The build loops over a list of (bra pair, ket pair) rows with parallel for. Each row writes a disjoint set of packed-8 slots, so the rows are independent, and the default (non-OpenMP) build runs them in order with the same result.

One integral at a time. mderi_prim1 computes a single (ab|cd) over four primitive components: E only for those powers, R only inside the box they need (herm_rfill_box), and a plain Hermite sum. It is for spot checks where a full block is wasted work, and it shares no matrix code with the block path, so the tests hold the two against each other.

Measured cost (native, cc -O2, Apple M2 Pro). A full (dd|dd) block of single primitives, 1296 integrals, takes 12.7 us; a (pp|pp) block, 81 integrals, 1.1 us: about 10 and 14 ns per integral. One (xx,xx|xx,xx) value through mderi_prim1 takes 0.67 us and one (x,x|x,x) 0.34 us, against 9.67 us and 1.11 us for the legacy lib/gto_md. The water 6-31G build (4186 values, 41,050 primitive quartets) takes 2.7 ms against the legacy 53.8 ms.

PySCF is not always the better reference. For the prims cases, PySCF's value and a 50-digit mpmath evaluation differ by more than 5 × 10−13 relative in 7 of 1536 values, by up to 3.5 × 10−11 for an (ff|ff) pair far apart, where this module is within 5.3 × 10−14 of mpmath. The tests use the mpmath value for those 7 (mderi_prims_fix).

Output is packed-8 (lib/hf/eri_store) over unit-normalized Cartesian AOs.

Used with import hf.md_eri.

Look inside this program

  • Atlas

    The program’s map: every function and which calls which.

  • There’s no recorded run of this program yet.

Shift+Enter in a cell runs it and moves to the next. Running a cell runs every cell above it too, as one program.

[1]lines 80–872