# Build your own nuclear DFT -- step 4: from orbitals back to densities.
#
# Step 3 turned a potential into orbitals. Density functional theory needs the
# reverse map too: turn occupied orbitals into the three local densities the
# Skyrme force feeds on,
#
#   rho(r) = sum (2j+1)/4pi * R^2               number density
#   tau(r) = sum (2j+1)/4pi * (R'^2 + l(l+1)/r^2 R^2)     kinetic density
#   J(r)   = sum (2j+1)/4pi * <sigma.l>/r * R^2           spin-orbit density
#
# where R(r) = u(r)/(r sqrt(h)) recovers the properly normalized radial wave
# function from the unit-norm eigenvector. The sum runs over OCCUPIED orbitals
# only -- for 16O that is 1s1/2, 1p3/2, 1p1/2, filled by 8 neutrons and 8
# protons, which in this Coulomb-free step are identical: solve one species
# and double it.
#
# Sanity checks a real code lives on: integrating rho must count 16 nucleons;
# J should nearly vanish because 16O is spin-saturated (both 1p partners full,
# their <sigma.l> weights cancel: 1*(2*1+2) + (-2)*(2*1) = 0).

pi = 3.141592653589793
hbar2_2m = 20.7355
h = 0.1
nr = 140
amass = 16.0

r = zeros(nr)
for i to nr { r[i] = (i + 1.0) * h }

def integrate_r2(f, n, hh) {
    s = 0.5 * f[0] * r[0] * r[0] + 0.5 * f[n - 1.0] * r[n - 1.0] * r[n - 1.0]
    for i to n {
        if i > 0 {
            if i < n - 1.0 { s += f[i] * r[i] * r[i] }
        }
    }
    return 4.0 * pi * s * hh
}

# np.gradient-style derivative: central differences, one-sided 2nd order edges
def grad1(f, out, n, hh) {
    out[0] = (-1.5 * f[0] + 2.0 * f[1] - 0.5 * f[2]) / hh
    for i to n {
        if i > 0 {
            if i < n - 1.0 { out[i] = (f[i + 1] - f[i - 1]) / (2.0 * hh) }
        }
    }
    out[n - 1.0] = (1.5 * f[n - 1.0] - 2.0 * f[n - 2.0] + 0.5 * f[n - 3.0]) / hh
    return 0.0
}

v0 = -51.0
rws = 1.27 * pow(amass, 1.0 / 3.0)
aws = 0.67
u_pot = zeros(nr)
wso = zeros(nr)
for i to nr {
    f = 1.0 / (1.0 + exp((r[i] - rws) / aws))
    u_pot[i] = v0 * f
    wso[i] = 22.0 * (0.0 - f * (1.0 - f) / aws)
}

# ---- block solver (step 3, unchanged) ----------------------------------------
diag = zeros(nr)
off = zeros(nr)
u_new = zeros(nr)
u_cur = zeros(nr)
tc = zeros(nr)
td = zeros(nr)
ev = zeros(1024)
eps_k = zeros(2)

def build_h(l, sdl) {
    for i to nr {
        diag[i] = 2.0 * hbar2_2m / (h * h) + hbar2_2m * l * (l + 1.0) / (r[i] * r[i]) + u_pot[i] + sdl * wso[i] / r[i]
        if i < nr - 1.0 { off[i] = 0.0 - hbar2_2m / (h * h) }
    }
    return 0.0
}

def lowest_k(n, kk) {
    sigma = diag[0]
    for i to n {
        g = diag[i]
        if i > 0 { g = g - abs(off[i - 1.0]) }
        if i < n - 1.0 { g = g - abs(off[i]) }
        if g < sigma { sigma = g }
    }
    sigma = sigma - 1.0
    for s to kk {
        for i to n { u_new[i] = sin((s + 1.0) * pi * (i + 1.0) / (n + 1.0)) }
        lam = 0.0
        for sweep to 300 {
            for i to n { u_cur[i] = u_new[i] }
            tc[0] = off[0] / (diag[0] - sigma)
            td[0] = u_cur[0] / (diag[0] - sigma)
            for i to n {
                if i > 0 {
                    m = diag[i] - sigma - off[i - 1.0] * tc[i - 1.0]
                    if i < n - 1.0 { tc[i] = off[i] / m }
                    td[i] = (u_cur[i] - off[i - 1.0] * td[i - 1.0]) / m
                }
            }
            u_new[n - 1.0] = td[n - 1.0]
            k = n - 2.0
            while k >= 0.0 {
                u_new[k] = td[k] - tc[k] * u_new[k + 1.0]
                k = k - 1.0
            }
            for t to s {
                dot = 0.0
                for i to n { dot += ev[t * 512.0 + i] * u_new[i] }
                for i to n { u_new[i] = u_new[i] - dot * ev[t * 512.0 + i] }
            }
            nrm = 0.0
            for i to n { nrm += u_new[i] * u_new[i] }
            nrm = sqrt(nrm)
            for i to n { u_new[i] = u_new[i] / nrm }
            newlam = 0.0
            for i to n {
                newlam += diag[i] * u_new[i] * u_new[i]
                if i < n - 1.0 { newlam += 2.0 * off[i] * u_new[i] * u_new[i + 1.0] }
            }
            dl = abs(newlam - lam)
            lam = newlam
            if sweep > 2.0 {
                if dl < 1.0e-12 { break }
            }
        }
        eps_k[s] = lam
        for i to n { ev[s * 512.0 + i] = u_new[i] }
    }
    return 0.0
}

# ---- NEW: orbital -> densities ------------------------------------------------
rho = zeros(nr)
tau = zeros(nr)
jso = zeros(nr)
R_tmp = zeros(nr)
dR = zeros(nr)

def add_orbital(deg, l, sdl) {
    # take the freshly solved state 0 out of ev, rebuild R = u/(r sqrt h)
    for i to nr { R_tmp[i] = ev[0.0 * 512.0 + i] / sqrt(h) / r[i] }
    grad1(R_tmp, dR, nr, h)
    w = deg / (4.0 * pi)
    for i to nr {
        rho[i] = rho[i] + w * R_tmp[i] * R_tmp[i]
        tau[i] = tau[i] + w * (dR[i] * dR[i] + l * (l + 1.0) / (r[i] * r[i]) * R_tmp[i] * R_tmp[i])
        jso[i] = jso[i] + w * sdl / r[i] * R_tmp[i] * R_tmp[i]
    }
    return 0.0
}

# occupied orbitals of one species (8 nucleons), then double for N = Z
build_h(0.0, 0.0)
lowest_k(nr, 1.0)
add_orbital(2.0, 0.0, 0.0)      # 1s1/2

build_h(1.0, 1.0)
lowest_k(nr, 1.0)
add_orbital(4.0, 1.0, 1.0)      # 1p3/2

build_h(1.0, -2.0)
lowest_k(nr, 1.0)
add_orbital(2.0, 1.0, -2.0)     # 1p1/2

for i to nr {
    rho[i] = 2.0 * rho[i]       # neutrons + protons
    tau[i] = 2.0 * tau[i]
    jso[i] = 2.0 * jso[i]
}

a_check = integrate_r2(rho, nr, h)
show a_check          # 16: the orbitals hold exactly the nucleus

show rho              # compare step 1's guess: same size, more structure
show tau              # kinetic density: where the wavefunctions wiggle
show jso              # nearly zero everywhere: 16O is spin-saturated
