#| First derivative on the mesh: centered inside, second-order one-sided at
#| both ends (numpy.gradient with edge_order = 2).
def grad1(f: Arr, h, out: Arr) {
n = len(f)
out[0] = (-1.5 * f[0] + 2.0 * f[1] - 0.5 * f[2]) / h
for i from 1 to n - 1 {
out[i] = (f[i + 1] - f[i - 1]) / (2.0 * h)
}
out[n - 1] = (1.5 * f[n - 1] - 2.0 * f[n - 2] + 0.5 * f[n - 3]) / h
return 0
}
#| The radial Laplacian f'' + 2 f' / r, from two first derivatives.
def lap_r(f: Arr, r: Arr, h, out: Arr) {
n = len(f)
d1 = zeros(n)
d2 = zeros(n)
grad1(f, h, d1)
grad1(d1, h, d2)
for i to n {
out[i] = d2[i] + 2.0 * d1[i] / r[i]
}
return 0
}
#| 4 pi times the integral of f r^2 dr, by the trapezoid rule on the mesh.
def integrate_r2(f: Arr, r: Arr, h) {
n = len(f)
s = 0.0
for i to n {
s += f[i] * r[i] * r[i]
}
s -= 0.5 * (f[0] * r[0] * r[0] + f[n - 1] * r[n - 1] * r[n - 1])
return 4.0 * PI * h * s
}
#| The direct Coulomb potential of the proton density: the charge inside r
#| over r, plus the shells outside r.
def coulomb(rp: Arr, r: Arr, h, out: Arr) {
n = len(rp)
inner = 0.0
for i to n {
inner += rp[i] * r[i] * r[i]
out[i] = inner / r[i]
}
outer = 0.0
for k from 0 to n by 1 reversed {
out[k] = E2 * 4.0 * PI * h * (out[k] + outer)
outer += rp[k] * r[k]
}
return 0
}
#| The number of eigenvalues of the symmetric tridiagonal matrix (diag, off)
#| below x: the number of negative pivots in the triangular factorization of H - x (Sturm).
def count_below(diag: Arr, off: Arr, x) {
n = len(diag)
cnt = 0
p = diag[0] - x
if p < 0.0 {
cnt += 1
}
for i from 1 to n {
if p == 0.0 {
p = 1.0e-300
}
p = diag[i] - x - off[i - 1] * off[i - 1] / p
if p < 0.0 {
cnt += 1
}
}
return cnt
}
#| The lowest eigenvalue of the symmetric tridiagonal matrix (diag, off),
#| by bisection on the Sturm count between lo and hi, to EIG_TOL.
def lowest_eigenvalue(diag: Arr, off: Arr, lo, hi) {
a = lo
b = hi
while b - a > EIG_TOL {
mid = 0.5 * (a + b)
if count_below(diag, off, mid) > 0 {
b = mid
} else {
a = mid
}
}
return a
}
#| The eigenvector of (diag, off) whose eigenvalue is nearest sigma, by
#| INV_SWEEPS solves of (H - sigma) w = u with the Thomas algorithm, starting
#| from u, which holds the unit vector on return. Returns its Rayleigh quotient.
def inverse_iteration(diag: Arr, off: Arr, sigma, u: Arr) {
n = len(diag)
c = zeros(n)
d = zeros(n)
w = zeros(n)
for sweep to INV_SWEEPS {
c[0] = off[0] / (diag[0] - sigma)
d[0] = u[0] / (diag[0] - sigma)
for i from 1 to n {
m = diag[i] - sigma - off[i - 1] * c[i - 1]
if i < n - 1 {
c[i] = off[i] / m
}
d[i] = (u[i] - off[i - 1] * d[i - 1]) / m
}
w[n - 1] = d[n - 1]
for k from 0 to n - 1 by 1 reversed {
w[k] = d[k] - c[k] * w[k + 1]
}
nrm = sqrt(sum(w * w))
if sum(w * u) < 0.0 {
nrm = -nrm
}
for i to n {
u[i] = w[i] / nrm
}
}
lam = 0.0
for i to n {
lam += diag[i] * u[i] * u[i]
if i < n - 1 {
lam += 2.0 * off[i] * u[i] * u[i + 1]
}
}
return lam
}
#| Hartree-Fock of oxygen-16 with SLy5 on nr points of spacing h. Writes the
#| converged neutron and proton densities into dens[2, nr] and returns
#| [E/A, r_p, r_n, r_ch, the neutron 1s1/2 1p3/2 1p1/2 energies, the proton
#| ones, iterations].
def skyrme_o16(h, nr, dens: Arr) {
r = zeros(nr)
for i to nr {
r[i] = (i + 1) * h
}
nsh = len(SHELL_L)
# densities per kind (0 = neutrons, 1 = protons): rho, tau, J
rho = zeros(2, nr)
tau = zeros(2, nr)
jso = zeros(2, nr)
prof = zeros(nr)
for i to nr {
prof[i] = 1.0 / (1.0 + exp((r[i] - 1.2 * pow(A_NUC, 1.0 / 3.0)) / 0.6))
}
pn = integrate_r2(prof, r, h)
for i to nr {
rho[0, i] = prof[i] * N_N / pn
rho[1, i] = prof[i] * Z_P / pn
}
# orbitals: warm-start vectors and last energies, block k = q * nsh + s
uvec = zeros(2 * nsh, nr)
lam = zeros(2 * nsh)
for k to 2 * nsh {
for i to nr {
uvec[k, i] = 1.0
}
}
rt = zeros(nr)
tt = zeros(nr)
jt = zeros(nr)
lap_t = zeros(nr)
lap_q = zeros(nr)
drt = zeros(nr)
drq = zeros(nr)
sj = zeros(nr)
dsj = zeros(nr)
B = zeros(nr)
dB = zeros(nr)
U = zeros(nr)
W = zeros(nr)
V = zeros(nr)
diag = zeros(nr)
off = zeros(nr - 1)
u = zeros(nr)
R = zeros(nr)
dR = zeros(nr)
nrho = zeros(2, nr)
ntau = zeros(2, nr)
njso = zeros(2, nr)
rq = zeros(nr)
e_old = 0.0
E = 0.0
it = 0
done = 0
while done < 1 and it < MAX_ITER {
it += 1
for i to nr {
rt[i] = rho[0, i] + rho[1, i]
tt[i] = tau[0, i] + tau[1, i]
jt[i] = jso[0, i] + jso[1, i]
}
lap_r(rt, r, h, lap_t)
grad1(rt, h, drt)
fill(nrho, 0.0)
fill(ntau, 0.0)
fill(njso, 0.0)
for q to 2 {
for i to nr {
rq[i] = rho[q, i]
}
lap_r(rq, r, h, lap_q)
grad1(rq, h, drq)
for i to nr {
sj[i] = jt[i] + jso[q, i]
}
grad1(sj, h, dsj)
if q == 1 {
coulomb(rq, r, h, V)
for i to nr {
V[i] -= E2 * pow(3.0 / PI, 1.0 / 3.0) * pow(rq[i], 1.0 / 3.0)
}
}
for i to nr {
ra = pow(rt[i], ALPHA)
B[i] = HB + 0.125 * (T1 * (2.0 + X1) + T2 * (2.0 + X2)) * rt[i] + 0.125 * (T2 * (1.0 + 2.0 * X2) - T1 * (1.0 + 2.0 * X1)) * rq[i]
uc = T0 * ((1.0 + X0 / 2.0) * rt[i] - (X0 + 0.5) * rq[i])
uc += 0.25 * T1 * ((1.0 + X1 / 2.0) * (tt[i] - 1.5 * lap_t[i]) - (X1 + 0.5) * (tau[q, i] - 1.5 * lap_q[i]))
uc += 0.25 * T2 * ((1.0 + X2 / 2.0) * (tt[i] + 0.5 * lap_t[i]) + (X2 + 0.5) * (tau[q, i] + 0.5 * lap_q[i]))
rsq = rho[0, i] * rho[0, i] + rho[1, i] * rho[1, i]
uc += T3 / 12.0 * ((1.0 + X3 / 2.0) * (2.0 + ALPHA) * ra * rt[i] - (X3 + 0.5) * (2.0 * ra * rq[i] + ALPHA * ra / rt[i] * rsq))
uc -= W0 * (sj[i] / r[i] + 0.5 * dsj[i])
U[i] = uc
W[i] = 0.5 * W0 * (drt[i] + drq[i]) + 0.125 * (T1 - T2) * jso[q, i] - 0.125 * (T1 * X1 + T2 * X2) * jt[i]
}
grad1(B, h, dB)
for s to nsh {
l = SHELL_L[s]
j = SHELL_J[s]
sdl = j * (j + 1.0) - l * (l + 1.0) - 0.75
for i to nr {
bm = B[i]
if i > 0 {
bm = 0.5 * (B[i - 1] + B[i])
}
bp = 0.5 * (B[i] + HB)
if i < nr - 1 {
bp = 0.5 * (B[i] + B[i + 1])
off[i] = -bp / (h * h)
}
diag[i] = (bm + bp) / (h * h) + B[i] * l * (l + 1.0) / (r[i] * r[i]) + dB[i] / r[i] + U[i] + sdl * W[i] / r[i]
if q == 1 {
diag[i] += V[i]
}
}
k = q * nsh + s
for i to nr {
u[i] = uvec[k, i]
}
sigma = lam[k] - NEAR
if it == 1 {
# Gershgorin: every eigenvalue lies within diag[i] +- (|off[i-1]| + |off[i]|)
big = 0.0
for i to nr - 1 {
big = max(big, abs(off[i]))
}
sigma = lowest_eigenvalue(diag, off, min(diag) - 2.0 * big, max(diag) + 2.0 * big) - EIG_TOL
}
lam[k] = inverse_iteration(diag, off, sigma, u)
for i to nr {
uvec[k, i] = u[i]
R[i] = u[i] / (sqrt(h) * r[i])
}
grad1(R, h, dR)
wt = (2.0 * j + 1.0) / (4.0 * PI)
for i to nr {
nrho[q, i] += wt * R[i] * R[i]
ntau[q, i] += wt * (dR[i] * dR[i] + l * (l + 1.0) / (r[i] * r[i]) * R[i] * R[i])
njso[q, i] += wt * sdl / r[i] * R[i] * R[i]
}
}
}
rho = (1.0 - MIX) * rho + MIX * nrho
tau = (1.0 - MIX) * tau + MIX * ntau
jso = (1.0 - MIX) * jso + MIX * njso
E = skyrme_energy(rho, tau, jso, r, h)
if abs(E - e_old) < E_TOL and it > 3 {
done = 1
}
e_old = E
}
rn = zeros(nr)
rp = zeros(nr)
for i to nr {
rn[i] = rho[0, i] * r[i] * r[i]
rp[i] = rho[1, i] * r[i] * r[i]
dens[0, i] = rho[0, i]
dens[1, i] = rho[1, i]
}
r2p = integrate_r2(rp, r, h) / Z_P
r2n = integrate_r2(rn, r, h) / N_N
return [E / A_NUC, sqrt(r2p), sqrt(r2n), sqrt(r2p + 1.5 * FOLD * FOLD), lam[0], lam[1], lam[2], lam[3], lam[4], lam[5], it]
}
#| The total energy: the Skyrme energy density integrated, plus the direct
#| Coulomb energy and the Slater exchange energy of the protons.
def skyrme_energy(rho: Arr, tau: Arr, jso: Arr, r: Arr, h) {
nr = len(r)
rn = zeros(nr)
rp = zeros(nr)
for i to nr {
rn[i] = rho[0, i]
rp[i] = rho[1, i]
}
rt = rn + rp
drt = zeros(nr)
drn = zeros(nr)
drp = zeros(nr)
grad1(rt, h, drt)
grad1(rn, h, drn)
grad1(rp, h, drp)
H = zeros(nr)
vc = zeros(nr)
coulomb(rp, r, h, vc)
ec = zeros(nr)
for i to nr {
tn = tau[0, i]
tp = tau[1, i]
tt = tn + tp
jn = jso[0, i]
jp = jso[1, i]
jt = jn + jp
rsq = rn[i] * rn[i] + rp[i] * rp[i]
e = HB * tt + 0.5 * T0 * ((1.0 + X0 / 2.0) * rt[i] * rt[i] - (X0 + 0.5) * rsq)
e += 0.25 * T1 * ((1.0 + X1 / 2.0) * (rt[i] * tt + 0.75 * drt[i] * drt[i]) - (X1 + 0.5) * (rn[i] * tn + 0.75 * drn[i] * drn[i] + rp[i] * tp + 0.75 * drp[i] * drp[i]))
e += 0.25 * T2 * ((1.0 + X2 / 2.0) * (rt[i] * tt - 0.25 * drt[i] * drt[i]) + (X2 + 0.5) * (rn[i] * tn - 0.25 * drn[i] * drn[i] + rp[i] * tp - 0.25 * drp[i] * drp[i]))
e += T3 / 12.0 * pow(rt[i], ALPHA) * ((1.0 + X3 / 2.0) * rt[i] * rt[i] - (X3 + 0.5) * rsq)
e += (T1 - T2) / 16.0 * (jn * jn + jp * jp) - (T1 * X1 + T2 * X2) / 16.0 * jt * jt
e += 0.5 * W0 * (jt * drt[i] + jn * drn[i] + jp * drp[i])
H[i] = e
ec[i] = 0.5 * vc[i] * rp[i] - 0.75 * E2 * pow(3.0 / PI, 1.0 / 3.0) * pow(rp[i], 4.0 / 3.0)
}
return integrate_r2(H, r, h) + integrate_r2(ec, r, h)
}