Notebooks · lib/hf/rys_eri
Electron repulsion integrals by Rys quadrature
This is the third ERI algorithm in lib/hf, next to McMurchie-Davidson and Head-Gordon-Pople. It shares no code with either: it never builds Hermite expansions or Obara-Saika intermediates, and it does not call the Boys function. Its tests use Boys as the oracle for the quadrature rule.
The idea. A primitive ERI over Cartesian Gaussians reduces to
(ab|cd) = K * int_0^1 Ix(t) Iy(t) Iz(t) exp(-x t^2) dt
where x = rho |P - Q|^2 (P and Q are the product centers of the bra and
the ket, rho = pq/(p+q)), K collects the Gaussian prefactors, and each
I is a polynomial in u = t^2 of degree at most L/2, with
L = la + lb + lc + ld. The Boys function is the special case where all
three I are 1. A polynomial of degree L/2 in u is integrated exactly by
an n-point Gauss rule for the weight exp(-x u) / (2 sqrt(u)) on
[0, 1] once 2n - 1 >= L/2, so
n = floor(L/2) + 1
roots suffice: 1 for (ss|ss), 3 for (pp|pp), 5 for (dd|dd), 7 for (ff|ff).
The rule for weight x is the Rys quadrature; its moments are the Boys values,
sum_i w_i u_i^k = int_0^1 t^(2k) exp(-x t^2) dt = F_k(x), k < 2n,
which is what the first test checks.
Roots and weights. The rule changes with x, and every primitive quartet
has its own x, so the roots are computed on the spot:
- Discretize the measure. An 80-point Gauss-Legendre rule on
[-1, 1], folded to its 40 positive nodes, integratest^(2k) exp(-x t^2)on[0, 1]. Abovex = 80the integrand is negligible pastt = sqrt(80/x), so the nodes shrink onto[0, sqrt(80/x)]and the integrand the rule sees is never sharper thanexp(-80 v^2). The dropped tail is at most 7.7 × 10−21 ofF_13, the highest moment 7 roots use (the regularized upper incomplete gamma Q(13.5, 80), from mpmath). - Stieltjes. On the discrete measure, build the monic orthogonal polynomials by the three-term recurrence
pi_(k+1)(u) = (u - alpha_k) pi_k(u) - beta_k pi_(k-1)(u), readingalpha_kandbeta_koff discrete inner products. - Golub-Welsch. The roots are the eigenvalues of the Jacobi matrix with
alphaon the diagonal andsqrt(beta)beside it, and each weight isbeta_0(the total mass,F_0) times the squared first component of its unit eigenvector. The eigenproblem is tridiagonal and at most 7 by 7, so an implicit QL iteration that tracks only the first row of the eigenvector matrix does it.
Measured here, over the 16 seeded x of the first test (10−4 to 200): the
worst relative error in F_k, k < 2n, against lib/hf/boys is 8.9 × 10−16 at
n = 1 and 4.7 × 10−15 at n = 7. A numpy prototype of the same three steps
chose the constants: with 72 nodes and a cutoff of 100 its worst error
(n <= 5, x up to 105) was 3.9 × 10−12, and with 80 nodes and a cutoff of 80
it was 6.2 × 10−14 (n <= 7, x up to 106, numpy's eigensolver rather than
this QL). The node count and the cutoff move together.
Against PySCF, the worst absolute gap is 9.2 × 10−15 over the 512 prims picks
(every class through (ff|ff)) and 2.2 × 10−14 over the 4186 water 6-31G values.
Shifting C00 by one part in 107 makes the prims test fail at 4.0e-9.
The 2-D integrals. At a root u, each Cartesian direction separates.
The integral G(i, k) of (x - Ax)^i (x - Cx)^k against the root's 2-D
Gaussian obeys
G(i+1, k) = C00 G(i, k) + i B10 G(i-1, k) + k B00 G(i, k-1)
G(i, k+1) = D00 G(i, k) + k B01 G(i, k-1) + i B00 G(i-1, k)
with G(0, 0) = 1 and
B00 = u / (2(p+q))
B10 = (1 - q u/(p+q)) / (2p), B01 = (1 - p u/(p+q)) / (2q)
C00 = (P - A) - q u/(p+q) (P - Q)
D00 = (Q - C) + p u/(p+q) (P - Q)
G puts all the bra's angular momentum on A and the ket's on C. Writing
x - B = (x - A) + (A - B) and expanding the power moves it back:
I(a, b; c, d) = sum_i sum_j C(b,i) (A-B)^(b-i) C(d,j) (C-D)^(d-j) G(a+i, c+j)
Then (ab|cd) = K sum_r w_r Ix Iy Iz, with
K = 2 pi^(5/2) / (p q sqrt(p+q)) exp(-ab/p |AB|^2) exp(-cd/q |CD|^2).
Normalization. Everything is in the library's unit-Cartesian convention:
each primitive carries cn from
bas_contract, and each component its bas_nc factor.
Validated range. rys_applicable(L) is 1 for L <= RYS_LTOT_MAX = 12,
(ff|ff), the highest class lib/hfref/prims holds. Every prims case agrees
with PySCF there, and so does every water 6-31G value (tests below). The
coverage counters use it to decide which classes Rys may vote on.
Used with import hf.rys_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.