The DFT solver, as written
pw_common.viv
Geometry and reciprocal lattice, the GTH pseudopotential closed forms, plane-wave counting, the FFT grid, the exchange-correlation constants, occupations and the Fermi level, Anderson mixing.
11 functions
-
gth_proj— -
gth_vloc— -
legendre— -
pw_extent— -
fft_nice— -
wrap— -
lda_xc— -
electrons_at— -
fermi_level— -
fill_occupations— -
anderson_step—
# pw_common: what the dense and the iterative plane-wave engines share. # # dft/run.sh si | dft/run.sh si it (this file is a prelude to both engines) # # Kohn-Sham DFT in a plane-wave basis with separable norm-conserving # pseudopotentials (Goedecker-Teter-Hutter / Hartwigsen-Goedecker-Hutter), the # local-density approximation (the Teter-93 Pade fit -- the functional those # pseudopotentials were generated with), and dense diagonalization of H(k) at # every k-point. Self-consistent field by density mixing, then a band structure # along a path. Hartree atomic units throughout. # # The input deck (dft/si.viv) is ordinary Viv that sets the globals read here. # This file is everything that does not depend on HOW the Kohn-Sham equations # are solved: geometry, the pseudopotential in reciprocal space, plane-wave # bookkeeping, the FFT grid, exchange-correlation, occupations, and the density # mixer. dft/pwdft.viv (dense) and dft/pwdft_it.viv (iterative) come after it. # # EVERY formula below is checked against something that does not share its # derivation: dft/ref/verify_formulas.py tests the GTH closed forms against # radial quadrature of their real-space definitions and the LDA against libxc, # and dft/ref/pwdft_ref.py is an independent numpy implementation that this # program must agree with iteration by iteration. PI = 3.141592653589793 HA_EV = 27.211386245988 # ============================================================== 1. geometry #: NDIM the dimension of space: A has one row per lattice vector NDIM = len(A) #: OMEGA the cell volume; OMEGA_S keeps the sign of the triple product OMEGA_S = A[0, 0] * (A[1, 1] * A[2, 2] - A[1, 2] * A[2, 1]) - A[0, 1] * (A[1, 0] * A[2, 2] - A[1, 2] * A[2, 0]) + A[0, 2] * (A[1, 0] * A[2, 1] - A[1, 1] * A[2, 0]) OMEGA = abs(OMEGA_S) # Reciprocal vectors, one per row: b_i . a_j = 2 pi delta_ij. B = zeros(NDIM, NDIM) for i to NDIM { j = mod(i + 1.0, 3.0) k = mod(i + 2.0, 3.0) B[i, 0] = 2.0 * PI * (A[j, 1] * A[k, 2] - A[j, 2] * A[k, 1]) / OMEGA_S B[i, 1] = 2.0 * PI * (A[j, 2] * A[k, 0] - A[j, 0] * A[k, 2]) / OMEGA_S B[i, 2] = 2.0 * PI * (A[j, 0] * A[k, 1] - A[j, 1] * A[k, 0]) / OMEGA_S } # Atom positions in cartesian bohr, and the electron count. TAU = ATFRAC @ A NELEC = sum(take(ZION, ATSPEC)) # ====================================================== 2. projector channels # # A channel is one radial projector: (species, l, i). CH0[s, l] is the first # channel of that (s, l) block; NPRJ[s, l] says how many follow it. NCH = 0.0 CH0 = zeros(NSPEC, GTH_NL) for s to NSPEC { for l to GTH_NL { CH0[s, l] = NCH NCH = NCH + NPRJ[s, l] } } CHS = zeros(NCH) CHL = zeros(NCH) CHI = zeros(NCH) for s to NSPEC { for l to GTH_NL { for i to NPRJ[s, l] { CHS[CH0[s, l] + i] = s CHL[CH0[s, l] + i] = l CHI[CH0[s, l] + i] = i } } } # ============================================= 3. the GTH pseudopotential, in G PI54 = pow(PI, 1.25) # The radial projector p_i^l(K), including 1/sqrt(OMEGA). # Closed forms from HGH (1998); i is zero-based here. Defined so that # <K|V_nl|K'> = sum_l (2l+1)/(4 pi) P_l(cos) sum_ij p_i(K) h_ij p_j(K'). # Each is verified against radial quadrature of r^2 p(r) j_l(K r) in # dft/ref/verify_formulas.py -- a closed form can be wrong and still run. #: l angular momentum, 0..3 #: i which projector of that l, 0..2 #: r the projector radius r_l #: k |k + G| def gth_proj(l, i, r, k) { x2 = k * k * r * r e = exp(-0.5 * x2) * PI54 / sqrt(OMEGA) if l == 0.0 { if i == 0.0 { return 4.0 * sqrt(2.0 * r * r * r) * e } if i == 1.0 { return 8.0 * sqrt(2.0 * r * r * r / 15.0) * (3.0 - x2) * e } return 16.0 * sqrt(2.0 * r * r * r / 105.0) * (15.0 - 10.0 * x2 + x2 * x2) * e / 3.0 } if l == 1.0 { r5 = pow(r, 5.0) if i == 0.0 { return 8.0 * sqrt(r5 / 3.0) * k * e } if i == 1.0 { return 16.0 * sqrt(r5 / 105.0) * k * (5.0 - x2) * e } return 32.0 * sqrt(r5 / 1155.0) * k * (35.0 - 14.0 * x2 + x2 * x2) * e / 3.0 } if l == 2.0 { r7 = pow(r, 7.0) if i == 0.0 { return 8.0 * sqrt(2.0 * r7 / 15.0) * k * k * e } return 16.0 * sqrt(2.0 * r7 / 105.0) * k * k * (7.0 - x2) * e / 3.0 } return 16.0 * sqrt(pow(r, 9.0) / 105.0) * k * k * k * e } # The local pseudopotential of ONE atom of species s at |G| = g, with 1/OMEGA. # At g = 0 this is the finite part: the divergent -4 pi Z/(OMEGA g^2) cancels # against the Hartree and ion-ion terms of a neutral cell. def gth_vloc(s, g) { rl = RLOC[s] x2 = g * g * rl * rl poly = CLOC[s, 0] + CLOC[s, 1] * (3.0 - x2) + CLOC[s, 2] * (15.0 - 10.0 * x2 + x2 * x2) + CLOC[s, 3] * (105.0 - 105.0 * x2 + 21.0 * x2 * x2 - x2 * x2 * x2) gauss = sqrt(8.0 * PI * PI * PI) * rl * rl * rl if g < 1.0e-12 { return (2.0 * PI * ZION[s] * rl * rl + gauss * poly) / OMEGA } e = exp(-0.5 * x2) return (0.0 - 4.0 * PI * ZION[s] * e / (g * g) + gauss * e * poly) / OMEGA } def legendre(l, c) { if l == 0.0 { return 1.0 } if l == 1.0 { return c } if l == 2.0 { return 1.5 * c * c - 0.5 } return 2.5 * c * c * c - 1.5 * c } # ===================================================== 4. plane-wave bookkeeping # A rigorous bound on |m_i|: m_i = G . a_i / 2 pi, and |G| <= sqrt(2 ECUT) + |k|. AMAX = 0.0 BMAX = 0.0 for i to NDIM { AMAX = max(AMAX, sqrt(sum(A[i, :] * A[i, :]))) BMAX = max(BMAX, sqrt(sum(B[i, :] * B[i, :]))) } MM = floor((sqrt(2.0 * ECUT) + BMAX) * AMAX / (2.0 * PI)) + 1.0 NM = 2.0 * MM + 1.0 # At fractional k: how many plane waves satisfy |k + G|^2 / 2 <= ECUT, and # the largest |m_i| among them (it sizes the FFT grid). def pw_extent(k1, k2, k3) { n = 0.0 big = 0.0 for i1 to NM { for i2 to NM { for i3 to NM { m1 = i1 - MM m2 = i2 - MM m3 = i3 - MM f1 = m1 + k1 f2 = m2 + k2 f3 = m3 + k3 gx = f1 * B[0, 0] + f2 * B[1, 0] + f3 * B[2, 0] gy = f1 * B[0, 1] + f2 * B[1, 1] + f3 * B[2, 1] gz = f1 * B[0, 2] + f2 * B[1, 2] + f3 * B[2, 2] if 0.5 * (gx * gx + gy * gy + gz * gz) <= ECUT { n = n + 1.0 if abs(m1) > big { big = abs(m1) } if abs(m2) > big { big = abs(m2) } if abs(m3) > big { big = abs(m3) } } } } } return n, big } # --- the k-points: the SCF set, then every point of the band path ----------- NPTS = (NCORNER - 1.0) * NSEG + 1.0 PATHK = zeros(NPTS, NDIM) PATHX = zeros(NPTS) for c to NCORNER - 1.0 { for t to NSEG { PATHK[c * NSEG + t, :] = CORNER[c, :] + (CORNER[c + 1.0, :] - CORNER[c, :]) * t / NSEG } } PATHK[NPTS - 1.0, :] = CORNER[NCORNER - 1.0, :] #: PATHX the distance along the path to each point, in 1/bohr: the plot's x axis for p from 1 to NPTS { dk = (PATHK[p, :] - PATHK[p - 1.0, :]) @ B PATHX[p] = PATHX[p - 1.0] + sqrt(sum(dk * dk)) } #: NPWMAX the most plane waves any k-point needs: the size of every H #: MBASIS the largest |m_i| that appears in any basis: it sizes the FFT grid NPWMAX = 0.0 MBASIS = 0.0 ALLK = vcat(KFRAC, PATHK) for q to len(ALLK) { n, big = pw_extent(ALLK[q, 0], ALLK[q, 1], ALLK[q, 2]) NPWMAX = max(NPWMAX, n) MBASIS = max(MBASIS, big) } # ============================================================ 5. the FFT grid # # H needs V(G - G') for every pair of basis vectors, so the grid must hold # |m| up to 2 MBASIS without aliasing; the density needs exactly the same. # # Any side that large is CORRECT. The FFT is fastest when the side factors # into 2s, 3s and 5s (each has its own butterfly); a factor of 7 goes through # the generic one, several times slower, and anything larger through Bluestein's # detour, slower again. 4 MBASIS + 2 is twice an odd number, so it is usually a # bad side. Round up to the next good one: 34 -> 36, not 35. SMALL_PRIMES = [2.0, 3.0, 5.0] # The smallest integer >= n with no prime factor above 5. def fft_nice(n) { m = n found = false while not found { rest = m for i to len(SMALL_PRIMES) { while mod(rest, SMALL_PRIMES[i]) == 0.0 { rest = rest / SMALL_PRIMES[i] } } if rest == 1.0 { found = true } else { m = m + 1.0 } } return m } NG = fft_nice(4.0 * MBASIS + 2.0) NGRID = NG * NG * NG DV = OMEGA / NGRID def wrap(m) { return m - NG * floor(m / NG) } #: G2 |G|^2 on the grid; VPS the ionic local potential, with structure factors G2 = zeros(NG, NG, NG) VPS = czeros(NG, NG, NG) for i1 to NG { for i2 to NG { for i3 to NG { m1 = i1 m2 = i2 m3 = i3 if m1 >= NG / 2.0 { m1 = m1 - NG } if m2 >= NG / 2.0 { m2 = m2 - NG } if m3 >= NG / 2.0 { m3 = m3 - NG } gx = m1 * B[0, 0] + m2 * B[1, 0] + m3 * B[2, 0] gy = m1 * B[0, 1] + m2 * B[1, 1] + m3 * B[2, 1] gz = m1 * B[0, 2] + m2 * B[1, 2] + m3 * B[2, 2] g2 = gx * gx + gy * gy + gz * gz G2[i1, i2, i3] = g2 v = complex(0.0, 0.0) for a to NAT { ph = gx * TAU[a, 0] + gy * TAU[a, 1] + gz * TAU[a, 2] v = v + gth_vloc(ATSPEC[a], sqrt(g2)) * complex(cos(ph), 0.0 - sin(ph)) } VPS[i1, i2, i3] = v } } } # ===================================================== 6. exchange-correlation # # The Teter-93 Pade fit to the LDA -- one rational function of r_s for exchange # AND correlation. It is what the GTH-PADE pseudopotentials were generated # with, so using anything else would be inconsistent with them. Checked against # libxc's LDA_XC_TETER93 to 1e-10 in dft/ref/verify_formulas.py. XA0 = 0.4581652932831429 XA1 = 2.217058676663745 XA2 = 0.7405551735357053 XA3 = 0.01968227878617998 XB1 = 1.0 XB2 = 4.504130959426697 XB3 = 1.110667363742916 XB4 = 0.02359291751427506 RS0 = pow(3.0 / (4.0 * PI), 1.0 / 3.0) #: RHO_FLOOR the density is floored here before r_s = (3 / 4 pi rho)^(1/3) is taken. #: A MIXED density is an extrapolation and can dip below zero where the #: true one is small; log of that is NaN, and the NaN reaches every #: eigenvalue one SCF step later. (Copper did exactly this.) RHO_FLOOR = 1.0e-30 # The LDA on the real-space grid, whole arrays at a time: eps_xc(r), hartree # per electron, and v_xc(r) = d(rho eps_xc) / d rho. def lda_xc(rho) { rs = exp(log(max(rho, RHO_FLOOR)) * (0.0 - 1.0 / 3.0)) * RS0 num = XA0 + rs * (XA1 + rs * (XA2 + rs * XA3)) den = rs * (XB1 + rs * (XB2 + rs * (XB3 + rs * XB4))) dnum = XA1 + rs * (2.0 * XA2 + rs * 3.0 * XA3) dden = XB1 + rs * (2.0 * XB2 + rs * (3.0 * XB3 + rs * 4.0 * XB4)) exc = 0.0 - num / den return exc, exc + rs * (dnum * den - num * dden) / (den * den) / 3.0 } ZERO3 = zeros(NG, NG, NG) # ============================================================ 6b. occupations # The electron count at chemical potential mu, Fermi-Dirac of width SMEAR. def electrons_at(mu) { n = 0.0 for k to NKPT { for b to NBANDS { x = (EIG[k, b] - mu) / SMEAR if x > 200.0 { x = 200.0 } if x < -200.0 { x = -200.0 } n = n + KWT[k] * 2.0 / (1.0 + exp(x)) } } return n } #: FERMI_BISECTIONS how many times fermi_level halves its bracket FERMI_BISECTIONS = 200 # The Fermi level: bisect on the electron count. 200 halvings of a ~10 Ha # bracket is far past double precision, so this matches any other bisection. def fermi_level() { lo = min(EIG[0:NKPT, 0:NBANDS]) - 1.0 hi = max(EIG[0:NKPT, 0:NBANDS]) + 1.0 mu = 0.0 for it to FERMI_BISECTIONS { mu = 0.5 * (lo + hi) if electrons_at(mu) > NELEC { hi = mu } else { lo = mu } } return mu } # Fill FOCC. An insulator (SMEAR = 0) fills the lowest NELEC/2 bands; a metal # gets Fermi-Dirac occupations. Returns the electronic entropy S (so that the # free energy is E - SMEAR * S), zero for an insulator. def fill_occupations(mu) { ent = 0.0 for k to NKPT { for b to NBANDS { if SMEAR > 0.0 { x = (EIG[k, b] - mu) / SMEAR if x > 200.0 { x = 200.0 } if x < -200.0 { x = -200.0 } f = 2.0 / (1.0 + exp(x)) FOCC[k, b] = f h = 0.5 * f if h > 1.0e-300 and h < 1.0 - 1.0e-16 { ent = ent - 2.0 * KWT[k] * (h * log(h) + (1.0 - h) * log(1.0 - h)) } } else { FOCC[k, b] = 0.0 if b < NELEC / 2.0 { FOCC[k, b] = 2.0 } } } } return ent } # ======================================================== 7. density mixing # # Anderson acceleration over the last three iterates. With F = rho_out - rho_in # the residual, find the combination of recent iterates whose residual is # smallest (a 3x3 least-squares problem), then take a damped step from it. # Linear mixing -- rho += MIX * F -- is what this does on its first step, and # all it would ever do with no history; it is enough for silicon and hopeless # for copper, whose d-electrons respond so strongly that a plain damped step # either crawls or oscillates. The fixed point is the same either way. # # History lives in named variables because an array of arrays is not a Viv # value; whole-array arithmetic keeps every line at native speed. AND_R1 = ZERO3 AND_R2 = ZERO3 AND_R3 = ZERO3 AND_F1 = ZERO3 AND_F2 = ZERO3 AND_F3 = ZERO3 # The next input density, from the current one, its residual, and the history. #: nh how many earlier iterates are stored, 0..3 def anderson_step(rho, f, nh) { if nh == 0.0 { return rho + MIX * f } a = zeros(nh, nh) bv = zeros(nh) th = zeros(nh) df1 = f - AND_F1 df2 = f - AND_F2 df3 = f - AND_F3 a[0, 0] = sum(df1 * df1) bv[0] = sum(df1 * f) if nh >= 2.0 { a[0, 1] = sum(df1 * df2) a[1, 0] = a[0, 1] a[1, 1] = sum(df2 * df2) bv[1] = sum(df2 * f) } if nh >= 3.0 { a[0, 2] = sum(df1 * df3) a[2, 0] = a[0, 2] a[1, 2] = sum(df2 * df3) a[2, 1] = a[1, 2] a[2, 2] = sum(df3 * df3) bv[2] = sum(df3 * f) } tr = 0.0 for i to nh { tr = tr + a[i, i] } for i to nh { a[i, i] = a[i, i] + 1.0e-14 * tr } lin_solve(a, bv, th) rbar = rho - th[0] * (rho - AND_R1) fbar = f - th[0] * df1 if nh >= 2.0 { rbar = rbar - th[1] * (rho - AND_R2) fbar = fbar - th[1] * df2 } if nh >= 3.0 { rbar = rbar - th[2] * (rho - AND_R3) fbar = fbar - th[2] * df3 } return rbar + MIX * fbar } # SPDX-License-Identifier: MIT OR Apache-2.0 # SPDX-FileCopyrightText: 2026 The Viv authors
This is the output of viv doc pw_common.viv, unchanged. The compiler lifts every
#| (what), #|| (how) and #||| (why) line out of the code and
attaches it to the line it describes — so the line numbers here count code only.
Its glossary holds 13 variables; ask for one with viv whatis.
#[ … #]).
1
# pw_common: what the dense and the iterative plane-wave engines share. 2
# 3
# dft/run.sh si | dft/run.sh si it (this file is a prelude to both engines) 4
# 5
# Kohn-Sham DFT in a plane-wave basis with separable norm-conserving 6
# pseudopotentials (Goedecker-Teter-Hutter / Hartwigsen-Goedecker-Hutter), the 7
# local-density approximation (the Teter-93 Pade fit -- the functional those 8
# pseudopotentials were generated with), and dense diagonalization of H(k) at 9
# every k-point. Self-consistent field by density mixing, then a band structure 10
# along a path. Hartree atomic units throughout. 11
# 12
# The input deck (dft/si.viv) is ordinary Viv that sets the globals read here. 13
# This file is everything that does not depend on HOW the Kohn-Sham equations 14
# are solved: geometry, the pseudopotential in reciprocal space, plane-wave 15
# bookkeeping, the FFT grid, exchange-correlation, occupations, and the density 16
# mixer. dft/pwdft.viv (dense) and dft/pwdft_it.viv (iterative) come after it. 17
# 18
# EVERY formula below is checked against something that does not share its 19
# derivation: dft/ref/verify_formulas.py tests the GTH closed forms against 20
# radial quadrature of their real-space definitions and the LDA against libxc, 21
# and dft/ref/pwdft_ref.py is an independent numpy implementation that this 22
# program must agree with iteration by iteration. 23
24
PI = 3.141592653589793 25
HA_EV = 27.211386245988 26
27
# ============================================================== 1. geometry 28
29
NDIM = len(A) 30
31
OMEGA_S = A[0, 0] * (A[1, 1] * A[2, 2] - A[1, 2] * A[2, 1]) - A[0, 1] * (A[1, 0] * A[2, 2] - A[1, 2] * A[2, 0]) + A[0, 2] * (A[1, 0] * A[2, 1] - A[1, 1] * A[2, 0]) 32
OMEGA = abs(OMEGA_S) 33
34
# Reciprocal vectors, one per row: b_i . a_j = 2 pi delta_ij. 35
B = zeros(NDIM, NDIM) 36
for i to NDIM { 37
j = mod(i + 1.0, 3.0) 38
k = mod(i + 2.0, 3.0) 39
B[i, 0] = 2.0 * PI * (A[j, 1] * A[k, 2] - A[j, 2] * A[k, 1]) / OMEGA_S 40
B[i, 1] = 2.0 * PI * (A[j, 2] * A[k, 0] - A[j, 0] * A[k, 2]) / OMEGA_S 41
B[i, 2] = 2.0 * PI * (A[j, 0] * A[k, 1] - A[j, 1] * A[k, 0]) / OMEGA_S 42
} 43
44
# Atom positions in cartesian bohr, and the electron count. 45
TAU = ATFRAC @ A 46
NELEC = sum(take(ZION, ATSPEC)) 47
48
# ====================================================== 2. projector channels 49
# 50
# A channel is one radial projector: (species, l, i). CH0[s, l] is the first 51
# channel of that (s, l) block; NPRJ[s, l] says how many follow it. 52
NCH = 0.0 53
CH0 = zeros(NSPEC, GTH_NL) 54
for s to NSPEC { 55
for l to GTH_NL { 56
CH0[s, l] = NCH 57
NCH = NCH + NPRJ[s, l] 58
} 59
} 60
CHS = zeros(NCH) 61
CHL = zeros(NCH) 62
CHI = zeros(NCH) 63
for s to NSPEC { 64
for l to GTH_NL { 65
for i to NPRJ[s, l] { 66
CHS[CH0[s, l] + i] = s 67
CHL[CH0[s, l] + i] = l 68
CHI[CH0[s, l] + i] = i 69
} 70
} 71
} 72
73
# ============================================= 3. the GTH pseudopotential, in G 74
75
PI54 = pow(PI, 1.25) 76
77
# The radial projector p_i^l(K), including 1/sqrt(OMEGA). 78
# Closed forms from HGH (1998); i is zero-based here. Defined so that 79
# <K|V_nl|K'> = sum_l (2l+1)/(4 pi) P_l(cos) sum_ij p_i(K) h_ij p_j(K'). 80
# Each is verified against radial quadrature of r^2 p(r) j_l(K r) in 81
# dft/ref/verify_formulas.py -- a closed form can be wrong and still run. 82
def gth_proj(l, i, r, k) { 83
x2 = k * k * r * r 84
e = exp(-0.5 * x2) * PI54 / sqrt(OMEGA) 85
if l == 0.0 { 86
if i == 0.0 { return 4.0 * sqrt(2.0 * r * r * r) * e } 87
if i == 1.0 { return 8.0 * sqrt(2.0 * r * r * r / 15.0) * (3.0 - x2) * e } 88
return 16.0 * sqrt(2.0 * r * r * r / 105.0) * (15.0 - 10.0 * x2 + x2 * x2) * e / 3.0 89
} 90
if l == 1.0 { 91
r5 = pow(r, 5.0) 92
if i == 0.0 { return 8.0 * sqrt(r5 / 3.0) * k * e } 93
if i == 1.0 { return 16.0 * sqrt(r5 / 105.0) * k * (5.0 - x2) * e } 94
return 32.0 * sqrt(r5 / 1155.0) * k * (35.0 - 14.0 * x2 + x2 * x2) * e / 3.0 95
} 96
if l == 2.0 { 97
r7 = pow(r, 7.0) 98
if i == 0.0 { return 8.0 * sqrt(2.0 * r7 / 15.0) * k * k * e } 99
return 16.0 * sqrt(2.0 * r7 / 105.0) * k * k * (7.0 - x2) * e / 3.0 100
} 101
return 16.0 * sqrt(pow(r, 9.0) / 105.0) * k * k * k * e 102
} 103
104
# The local pseudopotential of ONE atom of species s at |G| = g, with 1/OMEGA. 105
# At g = 0 this is the finite part: the divergent -4 pi Z/(OMEGA g^2) cancels 106
# against the Hartree and ion-ion terms of a neutral cell. 107
def gth_vloc(s, g) { 108
rl = RLOC[s] 109
x2 = g * g * rl * rl 110
poly = CLOC[s, 0] + CLOC[s, 1] * (3.0 - x2) + CLOC[s, 2] * (15.0 - 10.0 * x2 + x2 * x2) + CLOC[s, 3] * (105.0 - 105.0 * x2 + 21.0 * x2 * x2 - x2 * x2 * x2) 111
gauss = sqrt(8.0 * PI * PI * PI) * rl * rl * rl 112
if g < 1.0e-12 { 113
return (2.0 * PI * ZION[s] * rl * rl + gauss * poly) / OMEGA 114
} 115
e = exp(-0.5 * x2) 116
return (0.0 - 4.0 * PI * ZION[s] * e / (g * g) + gauss * e * poly) / OMEGA 117
} 118
119
def legendre(l, c) { 120
if l == 0.0 { return 1.0 } 121
if l == 1.0 { return c } 122
if l == 2.0 { return 1.5 * c * c - 0.5 } 123
return 2.5 * c * c * c - 1.5 * c 124
} 125
126
# ===================================================== 4. plane-wave bookkeeping 127
128
# A rigorous bound on |m_i|: m_i = G . a_i / 2 pi, and |G| <= sqrt(2 ECUT) + |k|. 129
AMAX = 0.0 130
BMAX = 0.0 131
for i to NDIM { 132
AMAX = max(AMAX, sqrt(sum(A[i, :] * A[i, :]))) 133
BMAX = max(BMAX, sqrt(sum(B[i, :] * B[i, :]))) 134
} 135
MM = floor((sqrt(2.0 * ECUT) + BMAX) * AMAX / (2.0 * PI)) + 1.0 136
NM = 2.0 * MM + 1.0 137
138
# At fractional k: how many plane waves satisfy |k + G|^2 / 2 <= ECUT, and 139
# the largest |m_i| among them (it sizes the FFT grid). 140
def pw_extent(k1, k2, k3) { 141
n = 0.0 142
big = 0.0 143
for i1 to NM { 144
for i2 to NM { 145
for i3 to NM { 146
m1 = i1 - MM 147
m2 = i2 - MM 148
m3 = i3 - MM 149
f1 = m1 + k1 150
f2 = m2 + k2 151
f3 = m3 + k3 152
gx = f1 * B[0, 0] + f2 * B[1, 0] + f3 * B[2, 0] 153
gy = f1 * B[0, 1] + f2 * B[1, 1] + f3 * B[2, 1] 154
gz = f1 * B[0, 2] + f2 * B[1, 2] + f3 * B[2, 2] 155
if 0.5 * (gx * gx + gy * gy + gz * gz) <= ECUT { 156
n = n + 1.0 157
if abs(m1) > big { big = abs(m1) } 158
if abs(m2) > big { big = abs(m2) } 159
if abs(m3) > big { big = abs(m3) } 160
} 161
} 162
} 163
} 164
return n, big 165
} 166
167
# --- the k-points: the SCF set, then every point of the band path ----------- 168
NPTS = (NCORNER - 1.0) * NSEG + 1.0 169
PATHK = zeros(NPTS, NDIM) 170
PATHX = zeros(NPTS) 171
for c to NCORNER - 1.0 { 172
for t to NSEG { 173
PATHK[c * NSEG + t, :] = CORNER[c, :] + (CORNER[c + 1.0, :] - CORNER[c, :]) * t / NSEG 174
} 175
} 176
PATHK[NPTS - 1.0, :] = CORNER[NCORNER - 1.0, :] 177
for p from 1 to NPTS { 178
dk = (PATHK[p, :] - PATHK[p - 1.0, :]) @ B 179
PATHX[p] = PATHX[p - 1.0] + sqrt(sum(dk * dk)) 180
} 181
182
NPWMAX = 0.0 183
MBASIS = 0.0 184
ALLK = vcat(KFRAC, PATHK) 185
for q to len(ALLK) { 186
n, big = pw_extent(ALLK[q, 0], ALLK[q, 1], ALLK[q, 2]) 187
NPWMAX = max(NPWMAX, n) 188
MBASIS = max(MBASIS, big) 189
} 190
191
# ============================================================ 5. the FFT grid 192
# 193
# H needs V(G - G') for every pair of basis vectors, so the grid must hold 194
# |m| up to 2 MBASIS without aliasing; the density needs exactly the same. 195
# 196
# Any side that large is CORRECT. The FFT is fastest when the side factors 197
# into 2s, 3s and 5s (each has its own butterfly); a factor of 7 goes through 198
# the generic one, several times slower, and anything larger through Bluestein's 199
# detour, slower again. 4 MBASIS + 2 is twice an odd number, so it is usually a 200
# bad side. Round up to the next good one: 34 -> 36, not 35. 201
202
SMALL_PRIMES = [2.0, 3.0, 5.0] 203
204
# The smallest integer >= n with no prime factor above 5. 205
def fft_nice(n) { 206
m = n 207
found = false 208
while not found { 209
rest = m 210
for i to len(SMALL_PRIMES) { 211
while mod(rest, SMALL_PRIMES[i]) == 0.0 { 212
rest = rest / SMALL_PRIMES[i] 213
} 214
} 215
if rest == 1.0 { found = true } else { m = m + 1.0 } 216
} 217
return m 218
} 219
220
NG = fft_nice(4.0 * MBASIS + 2.0) 221
NGRID = NG * NG * NG 222
DV = OMEGA / NGRID 223
224
def wrap(m) { 225
return m - NG * floor(m / NG) 226
} 227
228
G2 = zeros(NG, NG, NG) 229
VPS = czeros(NG, NG, NG) 230
for i1 to NG { 231
for i2 to NG { 232
for i3 to NG { 233
m1 = i1 234
m2 = i2 235
m3 = i3 236
if m1 >= NG / 2.0 { m1 = m1 - NG } 237
if m2 >= NG / 2.0 { m2 = m2 - NG } 238
if m3 >= NG / 2.0 { m3 = m3 - NG } 239
gx = m1 * B[0, 0] + m2 * B[1, 0] + m3 * B[2, 0] 240
gy = m1 * B[0, 1] + m2 * B[1, 1] + m3 * B[2, 1] 241
gz = m1 * B[0, 2] + m2 * B[1, 2] + m3 * B[2, 2] 242
g2 = gx * gx + gy * gy + gz * gz 243
G2[i1, i2, i3] = g2 244
v = complex(0.0, 0.0) 245
for a to NAT { 246
ph = gx * TAU[a, 0] + gy * TAU[a, 1] + gz * TAU[a, 2] 247
v = v + gth_vloc(ATSPEC[a], sqrt(g2)) * complex(cos(ph), 0.0 - sin(ph)) 248
} 249
VPS[i1, i2, i3] = v 250
} 251
} 252
} 253
254
# ===================================================== 6. exchange-correlation 255
# 256
# The Teter-93 Pade fit to the LDA -- one rational function of r_s for exchange 257
# AND correlation. It is what the GTH-PADE pseudopotentials were generated 258
# with, so using anything else would be inconsistent with them. Checked against 259
# libxc's LDA_XC_TETER93 to 1e-10 in dft/ref/verify_formulas.py. 260
XA0 = 0.4581652932831429 261
XA1 = 2.217058676663745 262
XA2 = 0.7405551735357053 263
XA3 = 0.01968227878617998 264
XB1 = 1.0 265
XB2 = 4.504130959426697 266
XB3 = 1.110667363742916 267
XB4 = 0.02359291751427506 268
RS0 = pow(3.0 / (4.0 * PI), 1.0 / 3.0) 269
RHO_FLOOR = 1.0e-30 270
271
# The LDA on the real-space grid, whole arrays at a time: eps_xc(r), hartree 272
# per electron, and v_xc(r) = d(rho eps_xc) / d rho. 273
def lda_xc(rho) { 274
rs = exp(log(max(rho, RHO_FLOOR)) * (0.0 - 1.0 / 3.0)) * RS0 275
num = XA0 + rs * (XA1 + rs * (XA2 + rs * XA3)) 276
den = rs * (XB1 + rs * (XB2 + rs * (XB3 + rs * XB4))) 277
dnum = XA1 + rs * (2.0 * XA2 + rs * 3.0 * XA3) 278
dden = XB1 + rs * (2.0 * XB2 + rs * (3.0 * XB3 + rs * 4.0 * XB4)) 279
exc = 0.0 - num / den 280
return exc, exc + rs * (dnum * den - num * dden) / (den * den) / 3.0 281
} 282
283
ZERO3 = zeros(NG, NG, NG) 284
285
# ============================================================ 6b. occupations 286
287
# The electron count at chemical potential mu, Fermi-Dirac of width SMEAR. 288
def electrons_at(mu) { 289
n = 0.0 290
for k to NKPT { 291
for b to NBANDS { 292
x = (EIG[k, b] - mu) / SMEAR 293
if x > 200.0 { x = 200.0 } 294
if x < -200.0 { x = -200.0 } 295
n = n + KWT[k] * 2.0 / (1.0 + exp(x)) 296
} 297
} 298
return n 299
} 300
301
FERMI_BISECTIONS = 200 302
303
# The Fermi level: bisect on the electron count. 200 halvings of a ~10 Ha 304
# bracket is far past double precision, so this matches any other bisection. 305
def fermi_level() { 306
lo = min(EIG[0:NKPT, 0:NBANDS]) - 1.0 307
hi = max(EIG[0:NKPT, 0:NBANDS]) + 1.0 308
mu = 0.0 309
for it to FERMI_BISECTIONS { 310
mu = 0.5 * (lo + hi) 311
if electrons_at(mu) > NELEC { 312
hi = mu 313
} else { 314
lo = mu 315
} 316
} 317
return mu 318
} 319
320
# Fill FOCC. An insulator (SMEAR = 0) fills the lowest NELEC/2 bands; a metal 321
# gets Fermi-Dirac occupations. Returns the electronic entropy S (so that the 322
# free energy is E - SMEAR * S), zero for an insulator. 323
def fill_occupations(mu) { 324
ent = 0.0 325
for k to NKPT { 326
for b to NBANDS { 327
if SMEAR > 0.0 { 328
x = (EIG[k, b] - mu) / SMEAR 329
if x > 200.0 { x = 200.0 } 330
if x < -200.0 { x = -200.0 } 331
f = 2.0 / (1.0 + exp(x)) 332
FOCC[k, b] = f 333
h = 0.5 * f 334
if h > 1.0e-300 and h < 1.0 - 1.0e-16 { 335
ent = ent - 2.0 * KWT[k] * (h * log(h) + (1.0 - h) * log(1.0 - h)) 336
} 337
} else { 338
FOCC[k, b] = 0.0 339
if b < NELEC / 2.0 { FOCC[k, b] = 2.0 } 340
} 341
} 342
} 343
return ent 344
} 345
346
347
# ======================================================== 7. density mixing 348
# 349
# Anderson acceleration over the last three iterates. With F = rho_out - rho_in 350
# the residual, find the combination of recent iterates whose residual is 351
# smallest (a 3x3 least-squares problem), then take a damped step from it. 352
# Linear mixing -- rho += MIX * F -- is what this does on its first step, and 353
# all it would ever do with no history; it is enough for silicon and hopeless 354
# for copper, whose d-electrons respond so strongly that a plain damped step 355
# either crawls or oscillates. The fixed point is the same either way. 356
# 357
# History lives in named variables because an array of arrays is not a Viv 358
# value; whole-array arithmetic keeps every line at native speed. 359
AND_R1 = ZERO3 360
AND_R2 = ZERO3 361
AND_R3 = ZERO3 362
AND_F1 = ZERO3 363
AND_F2 = ZERO3 364
AND_F3 = ZERO3 365
366
# The next input density, from the current one, its residual, and the history. 367
def anderson_step(rho, f, nh) { 368
if nh == 0.0 { 369
return rho + MIX * f 370
} 371
a = zeros(nh, nh) 372
bv = zeros(nh) 373
th = zeros(nh) 374
df1 = f - AND_F1 375
df2 = f - AND_F2 376
df3 = f - AND_F3 377
a[0, 0] = sum(df1 * df1) 378
bv[0] = sum(df1 * f) 379
if nh >= 2.0 { 380
a[0, 1] = sum(df1 * df2) 381
a[1, 0] = a[0, 1] 382
a[1, 1] = sum(df2 * df2) 383
bv[1] = sum(df2 * f) 384
} 385
if nh >= 3.0 { 386
a[0, 2] = sum(df1 * df3) 387
a[2, 0] = a[0, 2] 388
a[1, 2] = sum(df2 * df3) 389
a[2, 1] = a[1, 2] 390
a[2, 2] = sum(df3 * df3) 391
bv[2] = sum(df3 * f) 392
} 393
tr = 0.0 394
for i to nh { 395
tr = tr + a[i, i] 396
} 397
for i to nh { 398
a[i, i] = a[i, i] + 1.0e-14 * tr 399
} 400
lin_solve(a, bv, th) 401
rbar = rho - th[0] * (rho - AND_R1) 402
fbar = f - th[0] * df1 403
if nh >= 2.0 { 404
rbar = rbar - th[1] * (rho - AND_R2) 405
fbar = fbar - th[1] * df2 406
} 407
if nh >= 3.0 { 408
rbar = rbar - th[2] * (rho - AND_R3) 409
fbar = fbar - th[2] * df3 410
} 411
return rbar + MIX * fbar 412
} 413
414
# SPDX-License-Identifier: MIT OR Apache-2.0 415
# SPDX-FileCopyrightText: 2026 The Viv authors