Notebooks · examples/geomopt
examples/ geomopt.viv
examples/geomopt.viv · 1 code cellOpen in the workbenchDownload .vivLook 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 1–343
# Geometry optimization by numerical gradients: relax a diatomic's bond length to # its equilibrium by finding where the RHF/STO-3G energy is stationary. # # energy(R) places the two atoms at separation R, rebuilds the basis + integrals, # runs the SCF, and returns E. A Newton step on the *numerical* first/second # derivatives of energy(R) drives R to the minimum. Default molecule: H2. # Validation: R converges to the HF/STO-3G equilibrium bond length (~1.35 bohr). pi = 3.141592653589793 n_prim = 3 # ─── McMurchie-Davidson integral engine (reads the bf* globals by index) ─── def boys_n(nn, T) { if T < 1.0e-12 { return 1.0 / (2.0 * nn + 1.0) } return simpson(fn(t) -> pow(t, 2.0 * nn) * exp(-T * t * t), 0.0, 1.0, 100) } def hermite_E(i, j, t, Qx, a, b) { if i < 0.0 { return 0.0 } if j < 0.0 { return 0.0 } if t < 0.0 { return 0.0 } if t > i + j { return 0.0 } p = a + b if i == 0.0 { if j == 0.0 { mu = a * b / p return exp(-mu * Qx * Qx) } xpb = a / p * Qx return (0.5 / p * hermite_E(i, j - 1.0, t - 1.0, Qx, a, b) + xpb * hermite_E(i, j - 1.0, t, Qx, a, b) + (t + 1.0) * hermite_E(i, j - 1.0, t + 1.0, Qx, a, b)) } xpa = -b / p * Qx return (0.5 / p * hermite_E(i - 1.0, j, t - 1.0, Qx, a, b) + xpa * hermite_E(i - 1.0, j, t, Qx, a, b) + (t + 1.0) * hermite_E(i - 1.0, j, t + 1.0, Qx, a, b)) } def hermite_R(t, u, v, nn, p, PCx, PCy, PCz, RPC2) { if t == 0.0 { if u == 0.0 { if v == 0.0 { return pow(-2.0 * p, nn) * boys_n(nn, p * RPC2) } res = PCz * hermite_R(t, u, v - 1.0, nn + 1.0, p, PCx, PCy, PCz, RPC2) if v > 1.0 { res += (v - 1.0) * hermite_R(t, u, v - 2.0, nn + 1.0, p, PCx, PCy, PCz, RPC2) } return res } res = PCy * hermite_R(t, u - 1.0, v, nn + 1.0, p, PCx, PCy, PCz, RPC2) if u > 1.0 { res += (u - 1.0) * hermite_R(t, u - 2.0, v, nn + 1.0, p, PCx, PCy, PCz, RPC2) } return res } res = PCx * hermite_R(t - 1.0, u, v, nn + 1.0, p, PCx, PCy, PCz, RPC2) if t > 1.0 { res += (t - 1.0) * hermite_R(t - 2.0, u, v, nn + 1.0, p, PCx, PCy, PCz, RPC2) } return res } def overlap_1d(i, j, Qx, a, b) { return hermite_E(i, j, 0.0, Qx, a, b) * sqrt(pi / (a + b)) } def kinetic_1d(i, j, Qx, a, b) { term = b * (2.0 * j + 1.0) * overlap_1d(i, j, Qx, a, b) term += -2.0 * b * b * overlap_1d(i, j + 2.0, Qx, a, b) term += -0.5 * j * (j - 1.0) * overlap_1d(i, j - 2.0, Qx, a, b) return term } def prim_overlap(a, b, mu, nu) { sx = overlap_1d(bfL[mu], bfL[nu], bfCx[mu] - bfCx[nu], a, b) sy = overlap_1d(bfM[mu], bfM[nu], bfCy[mu] - bfCy[nu], a, b) sz = overlap_1d(bfN[mu], bfN[nu], bfCz[mu] - bfCz[nu], a, b) return sx * sy * sz } def prim_kinetic(a, b, mu, nu) { sx = overlap_1d(bfL[mu], bfL[nu], bfCx[mu] - bfCx[nu], a, b) sy = overlap_1d(bfM[mu], bfM[nu], bfCy[mu] - bfCy[nu], a, b) sz = overlap_1d(bfN[mu], bfN[nu], bfCz[mu] - bfCz[nu], a, b) tx = kinetic_1d(bfL[mu], bfL[nu], bfCx[mu] - bfCx[nu], a, b) ty = kinetic_1d(bfM[mu], bfM[nu], bfCy[mu] - bfCy[nu], a, b) tz = kinetic_1d(bfN[mu], bfN[nu], bfCz[mu] - bfCz[nu], a, b) return tx * sy * sz + sx * ty * sz + sx * sy * tz } def prim_nuclear(a, b, mu, nu, Cx, Cy, Cz) { la = bfL[mu] ma = bfM[mu] na = bfN[mu] lb = bfL[nu] mb = bfM[nu] nb = bfN[nu] p = a + b Px = (a * bfCx[mu] + b * bfCx[nu]) / p Py = (a * bfCy[mu] + b * bfCy[nu]) / p Pz = (a * bfCz[mu] + b * bfCz[nu]) / p PCx = Px - Cx PCy = Py - Cy PCz = Pz - Cz RPC2 = PCx * PCx + PCy * PCy + PCz * PCz qx = bfCx[mu] - bfCx[nu] qy = bfCy[mu] - bfCy[nu] qz = bfCz[mu] - bfCz[nu] val = 0.0 for t to la + lb + 1.0 { for u to ma + mb + 1.0 { for v to na + nb + 1.0 { e = hermite_E(la, lb, t, qx, a, b) * hermite_E(ma, mb, u, qy, a, b) * hermite_E(na, nb, v, qz, a, b) val += e * hermite_R(t, u, v, 0.0, p, PCx, PCy, PCz, RPC2) } } } return 2.0 * pi / p * val } def prim_eri(a, b, c, d, mu, nu, lam, sig) { p = a + b q = c + d Px = (a * bfCx[mu] + b * bfCx[nu]) / p Py = (a * bfCy[mu] + b * bfCy[nu]) / p Pz = (a * bfCz[mu] + b * bfCz[nu]) / p Qx = (c * bfCx[lam] + d * bfCx[sig]) / q Qy = (c * bfCy[lam] + d * bfCy[sig]) / q Qz = (c * bfCz[lam] + d * bfCz[sig]) / q alpha = p * q / (p + q) PQx = Px - Qx PQy = Py - Qy PQz = Pz - Qz RPQ2 = PQx * PQx + PQy * PQy + PQz * PQz qx1 = bfCx[mu] - bfCx[nu] qy1 = bfCy[mu] - bfCy[nu] qz1 = bfCz[mu] - bfCz[nu] qx2 = bfCx[lam] - bfCx[sig] qy2 = bfCy[lam] - bfCy[sig] qz2 = bfCz[lam] - bfCz[sig] val = 0.0 for t1 to bfL[mu] + bfL[nu] + 1.0 { for u1 to bfM[mu] + bfM[nu] + 1.0 { for v1 to bfN[mu] + bfN[nu] + 1.0 { e1 = hermite_E(bfL[mu], bfL[nu], t1, qx1, a, b) * hermite_E(bfM[mu], bfM[nu], u1, qy1, a, b) * hermite_E(bfN[mu], bfN[nu], v1, qz1, a, b) for t2 to bfL[lam] + bfL[sig] + 1.0 { for u2 to bfM[lam] + bfM[sig] + 1.0 { for v2 to bfN[lam] + bfN[sig] + 1.0 { e2 = hermite_E(bfL[lam], bfL[sig], t2, qx2, c, d) * hermite_E(bfM[lam], bfM[sig], u2, qy2, c, d) * hermite_E(bfN[lam], bfN[sig], v2, qz2, c, d) sign = pow(-1.0, t2 + u2 + v2) val += e1 * e2 * sign * hermite_R(t1 + t2, u1 + u2, v1 + v2, 0.0, alpha, PQx, PQy, PQz, RPQ2) } } } } } } return 2.0 * pow(pi, 2.5) / (p * q * sqrt(p + q)) * val } def cont_overlap(mu, nu) { s = 0.0 for ka to n_prim { for kb to n_prim { s += bfC[mu, ka] * bfC[nu, kb] * prim_overlap(bfA[mu, ka], bfA[nu, kb], mu, nu) } } return s } def cont_kinetic(mu, nu) { s = 0.0 for ka to n_prim { for kb to n_prim { s += bfC[mu, ka] * bfC[nu, kb] * prim_kinetic(bfA[mu, ka], bfA[nu, kb], mu, nu) } } return s } def cont_nuclear(mu, nu, Cx, Cy, Cz) { s = 0.0 for ka to n_prim { for kb to n_prim { s += bfC[mu, ka] * bfC[nu, kb] * prim_nuclear(bfA[mu, ka], bfA[nu, kb], mu, nu, Cx, Cy, Cz) } } return s } def cont_eri(mu, nu, lam, sig) { s = 0.0 for ka to n_prim { for kb to n_prim { for kc to n_prim { for kd to n_prim { coef = bfC[mu, ka] * bfC[nu, kb] * bfC[lam, kc] * bfC[sig, kd] s += coef * prim_eri(bfA[mu, ka], bfA[nu, kb], bfA[lam, kc], bfA[sig, kd], mu, nu, lam, sig) } } } } return s } def set_bf(idx, cx, cy, cz, l, m, n, a0, a1, a2, c0, c1, c2) { bfCx[idx] = cx bfCy[idx] = cy bfCz[idx] = cz bfL[idx] = l bfM[idx] = m bfN[idx] = n bfA[idx, 0] = a0 bfA[idx, 1] = a1 bfA[idx, 2] = a2 lt = l + m + n bfC[idx, 0] = c0 * pow(2.0 * a0 / pi, 0.75) * pow(4.0 * a0, lt / 2.0) bfC[idx, 1] = c1 * pow(2.0 * a1 / pi, 0.75) * pow(4.0 * a1, lt / 2.0) bfC[idx, 2] = c2 * pow(2.0 * a2 / pi, 0.75) * pow(4.0 * a2, lt / 2.0) } # ─── STO-3G 1s/2sp exponents (rows indexed by Z); H and first row ─── e1s = zeros(11, 3) esp = zeros(11, 3) e1s[1, 0] = 3.42525091 e1s[1, 1] = 0.62391373 e1s[1, 2] = 0.16885540 e1s[8, 0] = 130.7093200 e1s[8, 1] = 23.8088610 e1s[8, 2] = 6.4436083 esp[8, 0] = 5.0331513 esp[8, 1] = 1.1695961 esp[8, 2] = 0.3803890 e1s[9, 0] = 166.6791300 e1s[9, 1] = 30.3608120 e1s[9, 2] = 8.2168207 esp[9, 0] = 6.4648032 esp[9, 1] = 1.5022812 esp[9, 2] = 0.4885885 # ─── Molecule: a diatomic. Atom 0 at the origin, atom 1 on the z-axis. ─── # Default: H2 (n_basis=2, n_occ=1). For HF use atom_Z 9,1 / n_basis 6 / n_occ 5. n_atoms = 2 atom_Z = zeros(n_atoms) atom_x = zeros(n_atoms) atom_y = zeros(n_atoms) atom_z = zeros(n_atoms) atom_Z[0] = 1.0 atom_Z[1] = 1.0 n_basis = 2 n_occ = 1 bfL = zeros(n_basis) bfM = zeros(n_basis) bfN = zeros(n_basis) bfCx = zeros(n_basis) bfCy = zeros(n_basis) bfCz = zeros(n_basis) bfA = zeros(n_basis, n_prim) bfC = zeros(n_basis, n_prim) # RHF/STO-3G energy as a function of the bond length R (bohr). def energy(rr) { atom_z[1] = rr # Build the basis at the current geometry (1s on H/He; +2s,2p on first row). idx = 0.0 for i to n_atoms { zz = atom_Z[i] cx = atom_x[i] cy = atom_y[i] cz = atom_z[i] set_bf(idx, cx, cy, cz, 0.0, 0.0, 0.0, e1s[zz, 0], e1s[zz, 1], e1s[zz, 2], 0.15432897, 0.53532814, 0.44463454) idx = idx + 1.0 if zz > 2.0 { set_bf(idx, cx, cy, cz, 0.0, 0.0, 0.0, esp[zz, 0], esp[zz, 1], esp[zz, 2], -0.09996723, 0.39951283, 0.70011547) idx = idx + 1.0 set_bf(idx, cx, cy, cz, 1.0, 0.0, 0.0, esp[zz, 0], esp[zz, 1], esp[zz, 2], 0.15591627, 0.60768372, 0.39195739) idx = idx + 1.0 set_bf(idx, cx, cy, cz, 0.0, 1.0, 0.0, esp[zz, 0], esp[zz, 1], esp[zz, 2], 0.15591627, 0.60768372, 0.39195739) idx = idx + 1.0 set_bf(idx, cx, cy, cz, 0.0, 0.0, 1.0, esp[zz, 0], esp[zz, 1], esp[zz, 2], 0.15591627, 0.60768372, 0.39195739) idx = idx + 1.0 } } for i to n_basis { nrm = 1.0 / sqrt(cont_overlap(i, i)) for k to n_prim { bfC[i, k] = bfC[i, k] * nrm } } # Integrals. S = zeros(n_basis, n_basis) H_core = zeros(n_basis, n_basis) for mu to n_basis { for nu to n_basis { S[mu, nu] = cont_overlap(mu, nu) v = 0.0 for at to n_atoms { v += -atom_Z[at] * cont_nuclear(mu, nu, atom_x[at], atom_y[at], atom_z[at]) } H_core[mu, nu] = cont_kinetic(mu, nu) + v } } eri = zeros(n_basis, n_basis, n_basis, n_basis) for mu to n_basis { for nu to n_basis { for lam to n_basis { for sig to n_basis { eri[mu, nu, lam, sig] = cont_eri(mu, nu, lam, sig) } } } } # Symmetric orthogonalization. sv = zeros(n_basis) svec = zeros(n_basis, n_basis) eig_sym(S, sv, svec) dih = zeros(n_basis, n_basis) for i to n_basis { dih[i, i] = 1.0 / sqrt(sv[i]) } X = svec @ dih @ transpose(svec) # Nuclear repulsion (diatomic). Enuc = atom_Z[0] * atom_Z[1] / rr # SCF (core guess + light damping). P = zeros(n_basis, n_basis) Etot = 0.0 Eold = 0.0 for iter to 100 { G = zeros(n_basis, n_basis) for mu to n_basis { for nu to n_basis { g = 0.0 for lam to n_basis { for sig to n_basis { g += P[lam, sig] * (eri[mu, nu, lam, sig] - 0.5 * eri[mu, lam, nu, sig]) } } G[mu, nu] = g } } F = H_core + G Fp = transpose(X) @ F @ X ev = zeros(n_basis) Cp = zeros(n_basis, n_basis) eig_sym(Fp, ev, Cp) C = X @ Cp Pn = zeros(n_basis, n_basis) for mu to n_basis { for nu to n_basis { ps = 0.0 for a to n_occ { ps += C[mu, a] * C[nu, a] } Pn[mu, nu] = 2.0 * ps } } Ee = 0.0 for mu to n_basis { for nu to n_basis { Ee += 0.5 * Pn[mu, nu] * (H_core[mu, nu] + F[mu, nu]) } } Etot = Ee + Enuc for mu to n_basis { for nu to n_basis { P[mu, nu] = 0.5 * Pn[mu, nu] + 0.5 * P[mu, nu] } } dd = Etot - Eold if dd < 0.0 { dd = -dd } if dd < 1.0e-11 { break } Eold = Etot } return Etot } # ─── Optimize R by Newton on numerical derivatives of energy(R). ─── h = 0.0005 R = 1.20 for it to 10 { e0 = energy(R) ep = energy(R + h) em = energy(R - h) grad = (ep - em) / (2.0 * h) curv = (ep - 2.0 * e0 + em) / (h * h) R = R - grad / curv show R } Re = R Emin = energy(R) show Re show Emin # SPDX-License-Identifier: MIT OR Apache-2.0 # SPDX-FileCopyrightText: 2026 The Viv authors