def improve_continuum_orbital(atomlist, phi0, potential_ee, E, max_iter=100, thresh=1.0e-6): """ improves an initial guess phi0 for a continuum orbital with energy E by repeatedly computing orbital corrections phi_{i+1} = a_i * phi_{i} + b_i * dphi_{i} The orbital correction delta_phi is obtained as the solution of the inhomogeneous Schroedinger equation (H-E) dphi = -(H-E) phi i i The Schroedinger equation is solved approximately by replacing the effective potential with a spherical average around each atom. The best linear combination of phi and the correction is chosen. The coefficients a_i and b_i minimize the expectation value of the residual 2 R = (H-E) 2 a ,b = argmin <phi |(H-E) |phi > i i i+1 i+1 Parameters ========== atomlist : list of tuples (Zat,[x,y,z]) with atomic numbers and positions phi0 : callable, phi0(x,y,z) evaluates the initial orbital potential_ee : callable, potential_ee(x,y,z) evaluates the effective molecular Kohn-Sham potential WITHOUT the nuclear attraction, Vee = Vcoul + Vxc E : float, energy of continuum orbital Optional ======== max_iter : int, maximum number of orbital correction thresh : float, the iteration is stopped as soon as the weight of the orbital correction falls below this threshold: 2 b_i <= thresh Returns ======= phi : callable, phi(x,y,z) evaluates the improved orbital """ # attraction potential between nuclei and electrons nuclear_potential = nuclear_potential_func(atomlist) # effective potential (electron-electron interaction + nuclei-electrons) def potential(x, y, z): return potential_ee(x, y, z) + nuclear_potential(x, y, z) print("orbital corrections...") phi = phi0 for i in range(0, max_iter): residual = residual_ee_func(atomlist, phi, potential_ee, E) def source(x, y, z): return -residual(x, y, z) # orbital correction delta_phi = inhomogeneous_schroedinger(atomlist, potential, source, E) a, b = variational_mixture_continuum(atomlist, phi, delta_phi, potential_ee, E) # The error is estimated as the norm^2 of the orbital correction: # error = <delta_phi|delta_phi> # For large radii, we cannot expect the solution to be correct since # the density of points is far too low. Therefore we exclude all points # outside the radius `rlarge` from the integration. rlarge = 10.0 def error_density(x, y, z): err = abs(delta_phi(x, y, z))**2 r = np.sqrt(x**2 + y**2 + z**2) err[rlarge < r] = 0.0 return err error = integral(atomlist, error_density) print(" iteration i= %d |dphi|^2= %e b^2= %e (threshold= %e )" % (i + 1, error, b**2, thresh)) # The solution is converged, when the weight of the # correction term b**2 is small enough. if b**2 < thresh: print("CONVERGED") break # next approximation for phi phi_next = add_two_functions(atomlist, phi, delta_phi, a, b) ### DEBUG import matplotlib.pyplot as plt plt.clf() # plot cuts along z-axis r = np.linspace(-40.0, 40.0, 10000) x = 0.0 * r y = 0.0 * r z = r plt.title("Iteration i=%d" % (i + 1)) plt.xlabel("z / bohr") plt.ylim((-0.6, +0.6)) plt.plot(r, phi(x, y, z), label=r"$\phi$") plt.plot(r, delta_phi(x, y, z), ls="--", label=r"$\Delta \phi$") plt.plot(r, phi_next(x, y, z), label=r"$a \phi + b \Delta \phi$") plt.plot(r, residual(x, y, z), ls="-.", label="residual $(H-E)\phi$") plt.legend() plt.savefig("/tmp/iteration_%3.3d.png" % (i + 1)) # plt.show() ### # prepare for next iteration phi = phi_next else: msg = "Orbital corrections did not converge in '%s' iterations!" % max_iter print("WARNING: %s" % msg) #raise RuntimeError(msg) return phi
def test_lcao_continuum(): import matplotlib.pyplot as plt # bond length in bohr dist = 2.0 # positions of protons posH1 = (0.0, 0.0, -dist / 2.0) posH2 = (0.0, 0.0, +dist / 2.0) atomlist = [(1, posH1), (1, posH2)] # Set resolution of multicenter grid settings.radial_grid_factor = 20 settings.lebedev_order = 23 # energy of continuum orbital E = 1.0 # same functional as used in the calculation of pseudo orbitals xc = XCFunctionals.libXCFunctional(Parameters.pseudo_orbital_x, Parameters.pseudo_orbital_c) dft = BasissetFreeDFT(atomlist, xc) print("initial orbital guess from DFTB calculation") orbitals = dft.getOrbitalGuess() norb = len(orbitals) # all orbitals are doubly occupied nelec = 2 * norb bound_orbitals = dft.getOrbitalGuess() # effective potential rho = density_func(bound_orbitals) veff = effective_potential_func(atomlist, rho, xc, nelec=nelec) ps = AtomicPotentialSet(atomlist) r = np.linspace(-15.0, 15.0, 10000) x = 0.0 * r y = 0.0 * r z = r for lmax in [0, 1, 2, 3]: bs = AtomicScatteringBasisSet(atomlist, E, lmax=lmax) #test_AO_basis(atomlist, bs, ps, E) R = residual2_matrix(atomlist, veff, ps, bs) S = continuum_overlap(bs.bfs, E) print("continuum overlap") print(S) print("residual^2 matrix") print(R) eigvals, eigvecs = sla.eigh(R, S) print(eigvals) print("eigenvector belonging to lowest eigenvalue") print(eigvecs[:, 0]) # LCAO continuum orbitals continuum_orbitals = orbital_transformation(atomlist, bs.bfs, eigvecs) # improve continuum orbital by adding a correction term # # phi = phi0 + dphi # # The orbital correction dphi is the solution of the inhomogeneous # Schroedinger equation # # (H-E)dphi = -(H-E)phi0 # print("orbital correction...") phi0 = continuum_orbitals[0] phi = improve_continuum_orbital(atomlist, phi0, veff, E) exit(-1) residual_0 = residual_func(atomlist, phi0, veff, E) def source(x, y, z): return -residual_0(x, y, z) delta_phi = inhomogeneous_schroedinger(atomlist, veff, source, E) residual_d = residual_func(atomlist, delta_phi, veff, E) a, b = variational_mixture_continuum(atomlist, phi0, delta_phi, veff, E) phi = add_two_functions(atomlist, phi0, delta_phi, a, b) residual = residual_func(atomlist, phi, veff, E) plt.plot(r, 1.0 / np.sqrt(2.0) * bs.bfs[0](x, y, z), label=r"AO") plt.plot(r, phi0(x, y, z), label=r"$\phi_0$") plt.plot(r, delta_phi(x, y, z), label=r"$\Delta \phi$") plt.plot(r, phi(x, y, z), label=r"$\phi_0 + \Delta \phi$") plt.legend() plt.show() """ dphi = delta_phi(x,y,z) imin = np.argmin(abs(r-1.0)) dphi[abs(r) < 1.0] = dphi[imin] - (dphi[abs(r) < 1.0] - dphi[imin]) plt.plot(r, dphi, label=r"$\Delta \phi$") """ plt.plot(r, residual_0(x, y, z), label=r"$(H-E) \phi_0$") plt.plot(r, residual_d(x, y, z), label=r"$(H-E)\Delta \phi$") plt.plot(r, residual(x, y, z), label=r"$(H-E)(a \phi_0 + b \Delta \phi)$") plt.plot(r, a * residual_0(x, y, z) + b * residual_d(x, y, z), ls="-.", label=r"$(H-E)(a \phi_0 + b \Delta \phi)$ (separate)") plt.legend() plt.show() averaged_angular_distribution(atomlist, bound_orbitals, continuum_orbitals, E) # save continuum MOs to cubefiles for i, phi in enumerate(continuum_orbitals): def func(grid, dV): x, y, z = grid return phi(x, y, z) Cube.function_to_cubefile( atomlist, func, filename="/tmp/cmo_lmax_%2.2d_orb%4.4d.cube" % (lmax, i), ppb=5.0) # for i, phi in enumerate(continuum_orbitals): residual = residual_func(atomlist, phi, veff, E) delta_e = energy_correction(atomlist, residual, phi, method="Becke") print(" orbital %d energy <%d|H-E|%d> = %e" % (i, i, i, delta_e)) l, = plt.plot(r, phi(x, y, z), label=r"$\phi_{%d}$ ($l_{max}$ = %d)" % (i, lmax)) plt.plot(r, residual(x, y, z), ls="-.", label=r"$(H-E)\phi_{%d}$" % i, color=l.get_color()) plt.legend() plt.show()