def test_AO_basis(atomlist, bs, ps, E): """ check that atomic continuum orbitals are solutions of the atomic Kohn-Sham equations (T + Vi - E) \psi_i = 0 """ import matplotlib.pyplot as plt r = np.linspace(-5.0, 5.0, 10000) x = 0.0 * r y = 0.0 * r z = r nb = len(bs.bfs) for i in range(0, nb): ui = bs.bfs[i] # effective potential of the atom to which the basis function belongs I = ui.atom_index poti = ps.pots[I] # compute residual (T+Vi-E)ui residual_i = residual_func([atomlist[I]], ui, poti, E) l, = plt.plot(r, ui(x, y, z), label=r"$\psi_{%d}$" % (i + 1)) plt.plot(r, residual_i(x, y, z), ls="-.", label=r"$(T+V_{%d}-E)\phi_{%d}$" % (i + 1, i + 1), color=l.get_color()) plt.legend() plt.show()
def test_hmi_continuum(): """ check that the continuum wavefunction of H2+ really are solutions of Schroedinger's equation, i.e. have (H-E)\phi = 0 everywhere starting from an LCAO guess for the continuum orbital, try to find the exact solution by adding orbital corrections iteratively """ # First we compute the exact wavefunction of the hydrogen molecular ion. from DFTB.Scattering import HMI # The bond length and charges cannot be changed, since the # separation constants were calculated only for the H2+ ion at R=2! R = 2.0 Za = 1.0 Zb = 1.0 # energy of continuum orbital E = 0.5 ## sigma (m=0) orbital m = 0 n = 0 trig = 'cos' # separation constant Lsep = HMI.SeparationConstants(R, Za, Zb) Lsep.load_separation_constants() Lfunc = Lsep.L_interpolated(m, n) c2 = 0.5 * E * R**2 mL, nL, L = Lfunc(c2) parity = (-1)**(mL + nL) phi_exact = HMI.create_wavefunction(mL, L, R * (Za + Zb), 0.0, R, c2, parity, trig) # Old implementation of H2+ wavefunctions, the wavefunction # looks indistinguishable from the exact wavefunction, but the # non-zero residue shows that is contains large errors. from DFTB.Scattering.hydrogen_molecular_ion import DimerWavefunctions wfn = DimerWavefunctions(R, Za, Zb, plot=False) delta, (Rfunc, Sfunc, Pfunc), wavefunction_exact = wfn.getContinuumOrbital( m, n, trig, E) def phi_exact_DW(x, y, z): return wavefunction_exact((x, y, z), None) # Set resolution of multicenter grid settings.radial_grid_factor = 10 settings.lebedev_order = 41 # Next we compute the wavefunction using the basis set free method atomlist = [(int(Za), (0.0, 0.0, -R / 2.0)), (int(Zb), (0.0, 0.0, +R / 2.0))] # no other electrons, only nuclear potential def potential(x, y, z): nuc = 0.0 * x for Zi, posi in atomlist: ri = np.sqrt((x - posi[0])**2 + (y - posi[1])**2 + (z - posi[2])**2) nuc += -Zi / ri return nuc # electron-electron interaction def potential_ee(x, y, z): return 0.0 * x # Set resolution of multicenter grid settings.radial_grid_factor = 10 settings.lebedev_order = 41 # residual of exact wavefunction (should be zero) residual_exact = residual_func(atomlist, phi_exact, potential, E) residual_ee_exact = residual_ee_func(atomlist, phi_exact, potential_ee, E) residual_exact_DW = residual_func(atomlist, phi_exact_DW, potential, E) # Laplacian laplacian_exact = laplacian_func(atomlist, phi_exact) import matplotlib.pyplot as plt plt.clf() r = np.linspace(-15.0, 15.0, 5000) x = 0 * r y = 0 * r z = r # plot exact wavefunction plt.plot(r, phi_exact(x, y, z), label="$\phi$ exact") # phi_exact_xyz = phi_exact(x, y, z) phi_exact_DW_xyz = phi_exact_DW(x, y, z) scale = phi_exact_xyz.max() / phi_exact_DW_xyz.max() plt.plot(r, scale * phi_exact_DW_xyz, label="$\phi$ exact (DimerWavefunction)") # and residual plt.plot(r, residual_exact(x, y, z), label=r"$(H-E)\phi$ (exact, old)") plt.plot(r, residual_exact_DW(x, y, z), ls="-.", label=r"$(H-E)\phi$ (exact, DimerWavefunction, old)") plt.plot(r, residual_ee_exact(x, y, z), ls="--", label=r"$(H-E)\phi$ (exact, new)") # kinetic energy plt.plot(r, -0.5 * laplacian_exact(x, y, z), ls="--", label=r"$-\frac{1}{2}\nabla^2 \phi$") # potential energy plt.plot(r, (potential(x, y, z) - E) * phi_exact(x, y, z), ls="--", label=r"$(V-E)\phi$") ## The initial guess for the \sigma continuum orbital ## is a regular Coulomb function centered on the midpoint ## between the two protons. #phi0 = regular_coulomb_func(E, +2, 0, 0, 0.0, center=(0.0, 0.0, 0.0)) """ ## The initial guess for the \sigma continuum orbital is ## the sum of two hydrogen s continuum orbitals bs = AtomicScatteringBasisSet(atomlist, E, lmax=0) phi0 = add_two_functions(atomlist, bs.bfs[0], bs.bfs[1], 1.0/np.sqrt(2.0), 1.0/np.sqrt(2.0)) """ """ ## start with exact wavefunction phi0 = phi_exact """ # The initial guess for the \sigma continuum orbital is # a hydrogen continuum orbital in the center bs = AtomicScatteringBasisSet([(1, (0.0, 0.0, 0.0))], E, lmax=0) phi0 = bs.bfs[0] plt.plot(r, phi0(x, y, z), ls="-.", label="guess $\phi_0$") plt.legend() plt.show() #phi = improve_continuum_orbital(atomlist, phi0, potential_ee, E, thresh=1.0e-6) phi = relax_continuum_orbital(atomlist, phi0, potential_ee, E, thresh=1.0e-6) import matplotlib.pyplot as plt plt.clf() r = np.linspace(-15.0, 15.0, 5000) x = 0 * r y = 0 * r z = r phi_exact_xyz = phi_exact(x, y, z) phi_xyz = phi(x, y, z) # scale numerical phi such that the maxima agree scale = phi_exact_xyz.max() / phi_xyz.max() phi_xyz *= scale print("scaling factor s = %s" % scale) plt.plot(r, phi_exact_xyz, label="exact") plt.plot(r, phi_xyz, label="numerical") plt.legend() plt.show()
def lithium_cation_continuum(l, m, k): """ compute continuum orbital in the electrostatic potential of the Li^+ core Parameters ---------- l,m : angular quantum numbers of asymptotic solution e.g. l=0,m=0 s-orbital l=1,m=+1 px-orbital k : length of wavevector in a.u., the energy of the continuum orbital is E=1/2 k^2 """ # Li^+ atom atomlist = [(3, (0.0, 0.0, 0.0))] charge = +1 # choose resolution of multicenter grids for bound orbitals settings.radial_grid_factor = 20 # controls size of radial grid settings.lebedev_order = 25 # controls size of angular grid # 1s core orbitals for Li+^ atom RDFT = BasissetFreeDFT(atomlist, None, charge=charge) # bound_orbitals = RDFT.getOrbitalGuess() Etot, bound_orbitals, orbital_energies = RDFT.solveKohnSham() # choose resolution of multicenter grids for continuum orbitals settings.radial_grid_factor = 120 # controls size of radial grid settings.lebedev_order = 41 # controls size of angular grid # show number of radial and angular points in multicenter grid print_grid_summary(atomlist, settings.lebedev_order, settings.radial_grid_factor) print "electron density..." # electron density of two electrons in the 1s core orbital rho = density_func(bound_orbitals) print "effective potential..." # potential energy for Li nucleus and 2 core electrons potential = effective_potential_func(atomlist, rho, None, nelec=2) def v0(x, y, z): r = np.sqrt(x * x + y * y + z * z) return -1.0 / r def v1(x, y, z): return potential(x, y, z) - v0(x, y, z) # The continuum orbital is specified by its energy and asymptotic # angular momentum (E,l,m) # energy of continuum orbital E = 0.5 * k**2 # angular quantum numbers of asymptotic solution assert abs(m) <= l print " " print "Asymptotic continuum wavefunction" print "=================================" print " energy E= %e Hartree ( %e eV )" % ( E, E * AtomicData.hartree_to_eV) print " wavevector k= %e a.u." % k print " angular moment l= %d m= %+d" % (l, m) print " " # asymptotically correct solution for V0 = -1/r (hydrogen) Cf = regular_coulomb_func(E, charge, l, m, 0.0) phi0 = Cf # right-hand side of inhomogeneous Schroedinger equation def source(x, y, z): return -v1(x, y, z) * phi0(x, y, z) # # solve (H0 + V1 - E) dphi = - V1 phi0 # for orbital correction dphi atomic_numbers, atomic_coordinates = atomlist2arrays(atomlist) print "Schroedinger equation..." dphi = multicenter_inhomogeneous_schroedinger( potential, source, E, atomic_coordinates, atomic_numbers, radial_grid_factor=settings.radial_grid_factor, lebedev_order=settings.lebedev_order) # Combine asymptotically correct solution with correction # phi = phi0 + dphi phi = add_two_functions(atomlist, phi0, dphi, 1.0, 1.0) # residual for phi0 R0 = (H-E)phi0 residual0 = residual_func(atomlist, phi0, potential, E) # residual for final solution R = (H-E)phi residual = residual_func(atomlist, phi, potential, E) # spherical average of residual function residual_avg = spherical_average_residual_func(atomlist[0], residual) # The phase shift is determined by matching the radial wavefunction # to a shifted and scaled Coulomb function at a number of radial # sampling points drawn from the interval [rmin, rmax]. # On the one hand rmin < rmax should be chosen large enough, # so that the continuum orbital approaches its asymptotic form, # on the other hand rmax should be small enough that the accuracy # of the solution due to the sparse r-grid is still high enough. # A compromise has to be struck depending on the size of the radial grid. # The matching points are spread over several periods, # but not more than 30 bohr. wavelength = 2.0 * np.pi / k print "wavelength = %e" % wavelength rmin = 70.0 rmax = rmin + max(10 * wavelength, 30.0) Npts = 100 # determine phase shift and scaling factor by a least square # fit the the regular Coulomb function scale, delta = phaseshift_lstsq(atomlist, phi, E, charge, l, m, rmin, rmax, Npts) print "scale factor (relative to Coulomb wave) = %s" % scale print "phase shift (relative to Coulomb wave) = %e " % delta # normalize wavefunction, so that 1/scale phi(x,y,z) approaches # asymptotically a phase-shifted Coulomb wave phi_norm = multicenter_operation( [phi], lambda fs: fs[0] / scale, atomic_coordinates, atomic_numbers, radial_grid_factor=settings.radial_grid_factor, lebedev_order=settings.lebedev_order) # The continuum orbital should be orthogonal to the bound # orbitals belonging to the same Hamiltonian. I think this # should come out correctly by default. print " " print " Overlaps between bound orbitals and continuum orbital" print " =====================================================" for ib, bound_orbital in enumerate(bound_orbitals): olap_bc = overlap(atomlist, bound_orbital, phi_norm) print " <bound %d| continuum> = %e" % (ib + 1, olap_bc) print "" # shifted regular coulomb function Cf_shift = regular_coulomb_func(E, charge, l, m, delta) # save radial wavefunctions and spherically averaged residual # radial part of Coulomb wave without phase shift phi0_rad = radial_wave_func(atomlist, phi0, l, m) # radial part of shifted Coulomb wave Cf_shift_rad = radial_wave_func(atomlist, Cf_shift, l, m) # radial part of scattering solution phi_norm_rad = radial_wave_func(atomlist, phi_norm, l, m) print "" print "# RADIAL_WAVEFUNCTIONS" print "# Asymptotic wavefunction:" print "# charge Z= %+d" % charge print "# energy E= %e k= %e" % (E, k) print "# angular momentum l= %d m= %+d" % (l, m) print "# phase shift delta= %e rad" % delta print "# " print "# R/bohr Coulomb Coulomb radial wavefunction spherical avg. residual" print "# shifted R_{l,m}(r) <|(H-E)phi|^2>" import sys # write table to console r = np.linspace(1.0e-3, 100, 1000) data = np.vstack((r, phi0_rad(r), Cf_shift_rad(r), phi_rad(r), residual_avg(r))).transpose() np.savetxt(sys.stdout, data, fmt=" %+e ") print "# END" print ""
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()
def test_hmi_lcao_continuum(): """ """ # First we compute the exact wavefunction of the hydrogen molecular ion. from DFTB.Scattering import HMI R = 2.0 Za = 1.0 Zb = 1.0 E = 0.5 ## sigma (m=0) orbital m = 0 n = 0 L = m + n trig = 'cos' # separation constant Lsep = HMI.SeparationConstants(R, Za, Zb) Lsep.load_separation_constants() Lfunc = Lsep.L_interpolated(m, n) c2 = 0.5 * E * R**2 mL, nL, L = Lfunc(c2) parity = (-1)**(mL + nL) phi_exact = HMI.create_wavefunction(mL, L, R * (Za + Zb), 0.0, R, c2, parity, trig) """ from DFTB.Scattering.hydrogen_molecular_ion import DimerWavefunctions wfn = DimerWavefunctions(R,Za,Zb, plot=False) delta, (Rfunc,Sfunc,Pfunc),wavefunction_exact = wfn.getContinuumOrbital(m,n,trig,E) def phi_exact(x,y,z): return wavefunction_exact((x,y,z), None) """ # Next we compute the wavefunction using the basis set free method atomlist = [(int(Za), (0.0, 0.0, -R / 2.0)), (int(Zb), (0.0, 0.0, +R / 2.0))] # no other electrons, only nuclear potential def potential(x, y, z): nuc = 0.0 * x for Zi, posi in atomlist: ri = np.sqrt((x - posi[0])**2 + (y - posi[1])**2 + (z - posi[2])**2) nuc += -Zi / ri return nuc # Set resolution of multicenter grid settings.radial_grid_factor = 40 settings.lebedev_order = 21 # residual of exact wavefunction (should be zero) residual_exact = residual_func(atomlist, phi_exact, potential, E) # Laplacian laplacian_exact = laplacian_func(atomlist, phi_exact) import matplotlib.pyplot as plt plt.clf() r = np.linspace(-15.0, 15.0, 5000) x = 0 * r y = 0 * r z = r # plot exact wavefunction plt.plot(r, phi_exact(x, y, z), label="$\phi$ exact") # and residual plt.plot(r, residual_exact(x, y, z), label=r"$(H-E)\phi$ (exact)") # kinetic energy plt.plot(r, -0.5 * laplacian_exact(x, y, z), ls="--", label=r"$-\frac{1}{2}\nabla^2 \phi$") # potential energy plt.plot(r, (potential(x, y, z) - E) * phi_exact(x, y, z), ls="--", label=r"$(V-E)\phi$") # LCAO continuum orbitals ps = AtomicPotentialSet(atomlist) lmax = 4 bs = AtomicScatteringBasisSet(atomlist, E, lmax=lmax) R = residual2_matrix(atomlist, potential, 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]) continuum_orbitals = orbital_transformation(atomlist, bs.bfs, eigvecs) plt.cla() plt.plot(r, phi_exact(x, y, z), ls="--", label="$\phi$ (exact)") for i in range(0, len(continuum_orbitals)): plt.plot(r, continuum_orbitals[i](x, y, z)) plt.legend() plt.show()
def improve_continuum_orbital(atomlist, phi0, potential, 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 : callable, potential(x,y,z) evaluates the effective molecular Kohn-Sham potential V 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 """ print("orbital corrections...") phi = phi0 for i in range(0, max_iter): residual = residual_func(atomlist, phi, potential, 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, 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 variational_mixture_continuum(atomlist, phi, delta_phi, potential, E, rsmall=0.05, rlarge=10.0): """ Since the orbital correction is only approximate due to the spherical averaging, for the improved wavefunction we make the ansatz psi(r) = a phi(r) + b delta_phi(r) where the coefficients a and b are optimized variationally. a,b are the coefficients belonging to the smallest in magnitude eigenvalue of the generalized eigenvalue equation R v = S v or ( Raa Rab ) (a) ( Saa Sab ) ( a ) ( ) ( ) = E ( ) ( ) ( Rba Rbb ) (b) ( Sba Sbb ) ( b ) where Raa = <phi|(H-E)^2|phi>, Rab = <phi|(H-E)^2|delta_phi>, etc. are the matrix elements of the residual operator (H-E)^2 and Sab is the overlap between the two wavefunctions integrated over one one wavelength at a large distance. Parameters ========== atomlist : list of tuples (Zat,[x,y,z]) with atomic numbers and positions phi : callable, phi(x,y,z) evaluates the initial orbital delta_phi : callable, delta_phi(x,y,z) evaluates the orbital correction potential : callable, potential(x,y,z) evaluates the effective molecular Kohn-Sham potential V E : float, energy of continuum orbital, E=1/2 k^2 Optional ======== rsmall : regions closer than `rsmall` to any nucleus are excluded from the integration rlarge : regions further away from the origin than `rlarge` are also excluded, the overlap matrix is compute on the interval [rlarge, rlarge+2*pi/k] Returns ======= a,b : floats, mixing coefficients """ # (H-E)phi residual_a = residual_func(atomlist, phi, potential, E) # (H-E)delta_phi residual_b = residual_func(atomlist, delta_phi, potential, E) residuals = [residual_a, residual_b] n = len(residuals) R = np.zeros((n, n)) for a in range(0, n): for b in range(0, n): def integrand(x, y, z): integ = residuals[a](x, y, z) * residuals[b](x, y, z) # Around the nuclei, it is very difficult to accuratly # calculate the residual (H-E)phi, because both the nuclear attraction (negative) # and the kinetic energy (positive) are very large, but cancel mostly, # so that we get very large numerical errors. To avoid this, we excise a circular # region of radius rsmall around each nucleus and set the integrand # to zero in this region, so as to exclude regions with large errors from # the calculation. for i, (Zi, posi) in enumerate(atomlist): # distance of point (x,y,z) from nucleus i xi, yi, zi = posi ri = np.sqrt((x - xi)**2 + (y - yi)**2 + (z - zi)**2) integ[ri < rsmall] = 0.0 # Since density of the grid decreases very rapidly far away from the # molecule, we cannot expect the residual to be accurate at large # distances. Therefore we exclude all points outside the radius `rlarge` # from the integraion r = np.sqrt(x**2 + y**2 + z**2) integ[rlarge < r] = 0.0 return integ # compute the integrals R_ab = <a|(H-E)(H-E)|b> where |a> and |b> # are phi or delta_phi print("integration R[%d,%d]" % (a, b)) R[a, b] = integral(atomlist, integrand) print("matrix elements of R = (H-E)^2") print(R) # S = continuum_overlap([phi, delta_phi], E, rlarge=rlarge) print("continuum overlap matrix") print(S) # solve generalized eigenvalue problem eigvals, eigvecs = sla.eigh(R, S) print("eigenvalues") print(eigvals) print("eigenvectors") print(eigvecs) # select eigenvector with smallest (in magnitude) eigenvalue imin = np.argmin(abs(eigvals)) a, b = eigvecs[:, imin] # Eigenvectors come out with arbitrary signs, # the phase of phi should always be positive. if a < 0.0: a *= -1.0 b *= -1.0 return a, b
def hmi_variational_kohn(): # H2^+ # bond length in bohr R = 2.0 # positions of protons posH1 = (0.0, 0.0, -R/2.0) posH2 = (0.0, 0.0, +R/2.0) # center of charge center = (0.0, 0.0, 0.0) atomlist = [(1, posH1), (1, posH2)] charge = +2 # choose resolution of multicenter grids for continuum orbitals settings.radial_grid_factor = 10 # controls size of radial grid settings.lebedev_order = 21 # controls size of angular grid # energy of continuum orbital (in Hartree) E = 10.0 # angular momentum quantum numbers l,m = 0,0 rho = None xc = None print "effective potential..." V = effective_potential_func(atomlist, rho, xc, nelec=0) # basis functions u1 = regular_coulomb_func(E, +1, l, m, 0.0, center=posH1) eta1 = 0.0 u2 = regular_coulomb_func(E, +1, l, m, 0.0, center=posH2) eta2 = 0.0 # asymptotic solutions s0 = regular_coulomb_func(E, charge, l, m, 0.0, center=center) # c0 = irregular_coulomb_func(E, charge, l, m, 0.0, # center=center) c0 = regular_coulomb_func(E, charge, l, m, np.pi/2.0, center=center) # switching function # g(0) = 0 g(oo) = 1 def g(r): r0 = 3.0 # radius at which the switching happens gr = 0*r gr[r > 0] = 1.0/(1.0+np.exp(-(1-r0/r[r > 0]))) return gr def s(x,y,z): r = np.sqrt(x*x+y*y+z*z) return g(r) * s0(x,y,z) def c(x,y,z): r = np.sqrt(x*x+y*y+z*z) return g(r) * c0(x,y,z) free = [s,c] # basis potentials def V1(x,y,z): r2 = (x-posH1[0])**2 + (y-posH1[1])**2 + (z-posH1[2])**2 return -1.0/np.sqrt(r2) def V2(x,y,z): r2 = (x-posH2[0])**2 + (y-posH2[1])**2 + (z-posH2[2])**2 return -1.0/np.sqrt(r2) basis = [u1,u2] basis_potentials = [V1,V2] # number of basis functions nb = len(basis) # matrix elements between basis functions Lbb = np.zeros((nb,nb)) for i in range(0, nb): ui = basis[i] for j in range(0, nb): print "i= %d j= %d" % (i,j) uj = basis[j] Vj = basis_potentials[j] def integrand(x,y,z): return ui(x,y,z) * (V(x,y,z) - Vj(x,y,z)) * uj(x,y,z) Lbb[i,j] = integral(atomlist, integrand) print Lbb # matrix elements between unbound functions Lff = np.zeros((2,2)) for i in range(0, 2): fi = free[i] for j in range(0, 2): print "i= %d j= %d" % (i,j) fj = free[j] Lff[i,j] = kinetic(atomlist, fi,fj) + nuclear(atomlist, fi,fj) - E * overlap(atomlist, fi,fj) print Lff # matrix elements between bound and unbound functions Lbf = np.zeros((nb,2)) Lfb = np.zeros((2,nb)) for i in range(0, nb): ui = basis[i] for j in range(0, 2): fj = free[j] print "i= %d j= %d" % (i,j) Lbf[i,j] = kinetic(atomlist, ui,fj) + nuclear(atomlist, ui,fj) - E * overlap(atomlist, ui,fj) Lfb[j,i] = kinetic(atomlist, fj,ui) + nuclear(atomlist, fj,ui) - E * overlap(atomlist, fj,ui) print Lbf print Lfb # coeffs = -np.dot(la.inv(Lbb), Lbf) M = Lff - np.dot(Lfb, np.dot(la.inv(Lbb), Lbf)) print "M" print M # The equation # # M. (1) = 0 # (t) # cannot be fulfilled, usually. t0 = -M[0,0]/M[0,1] tans = np.array([np.tan(eta1), np.tan(eta2)]) t = (t0 + np.sum(coeffs[:,0]*tans) + t0*np.sum(coeffs[:,1]*tans)) \ / (1 + np.sum(coeffs[:,0]) + t0*np.sum(coeffs[:,1])) eta = np.arctan(t) # Because a global phase is irrelevant, the phase shift is only determined # modulo pi. sin(pi+eta) = -sin(eta) while eta < 0.0: eta += np.pi print "eta= %s" % eta bc0 = linear_combination(atomlist, basis, coeffs[:,0]) bc1 = linear_combination(atomlist, basis, coeffs[:,1]) #u = s + np.dot(basis, coeffs[:,0]) + t0 * (c + np.dot(basis, coeffs[:,1])) orbitals = [s, bc0, c, bc1] nrm = (1 + np.sum(coeffs[:,0]) + t0*np.sum(coeffs[:,1])) orb_coeffs = np.array([ 1.0, 1.0, t0, t0 ]) / nrm phi = linear_combination(atomlist, orbitals, orb_coeffs) residual = residual_func(atomlist, phi, V, E) import matplotlib.pyplot as plt r = np.linspace(-50.0, 50.0, 5000) x = 0.0*r y = 0.0*r z = r plt.plot(r, phi(x,y,z), label=r"$\phi$") plt.plot(r, residual(x,y,z), label=r"residual $(H-E)\phi$") plt.plot(r, V(x,y,z), label=r"potential") phi_lcao = linear_combination(atomlist, basis, [1.0/np.sqrt(2.0), 1.0/np.sqrt(2.0)]) residual_lcao = residual_func(atomlist, phi_lcao, V, E) plt.plot(r, phi_lcao(x,y,z), label=r"$\phi_{LCAO}$") plt.plot(r, residual_lcao(x,y,z), label=r"residual $(H-E)\phi_{LCAO}$") plt.legend() plt.show()