Esempio n. 1
0
    def normalize(self, atomlist):
        """
        compute and set the normalization constant so that the
        orbital is normalized to 1.

        Parameters
        ----------
        atomlist   :  molecule geometry for defining Becke's grid
                      for numerical integration numerical

        Returns
        -------
        norm       :  norm of wavefunction <wfn|wfn>^{1/2}
                      before normalization
        """
        # norm^2 of wavefunction <wfn|wfn>
        nrm2 = integral(atomlist,
                        lambda x, y, z: abs(self.__call__(x, y, z))**2)
        nrm = np.sqrt(nrm2)
        # update normalization constant
        self.norm *= nrm

        return nrm
Esempio n. 2
0
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
Esempio n. 3
0
def variational_mixture_continuum(atomlist,
                                  phi,
                                  delta_phi,
                                  potential_ee,
                                  E,
                                  rsmall=0.0,
                                  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_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, 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_ee_func(atomlist, phi, potential_ee, E)
    # (H-E)delta_phi
    residual_b = residual_ee_func(atomlist, delta_phi, potential_ee, 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
Esempio n. 4
0
def residual2_matrix(atomlist, potential, ps, bs):
    """
    compute matrix elements of (H-E)^2
                2
       <u |(H-E) |u > = <u |(V-V )(V-V )|u >
         i         j      i     i     j   j

    Parameters
    ----------
    atomlist  :  list of tuples (Zat,[x,y,z]) with atomic numbers
                 and positions
    potential :  callable, potential(x,y,z) evaluates the effective
                 molecular Kohn-Sham potential V
    ps        :  instance of `AtomicPotentialSet` with the effective
                 atomic Kohn-Sham potentials V_i
    bs        :  instance of `AtomicScatteringBasisSet` with the atomic
                 continuum orbitals |ui> (all for the same energy)
    """
    # number of basis functions
    nb = len(bs.bfs)
    R = np.zeros((nb, nb))

    for i in range(0, nb):
        # i-th basis function
        ui = bs.bfs[i]
        # effective potential of the atom to which the basis function belongs
        I = ui.atom_index
        poti = ps.pots[I]
        for j in range(i, nb):
            print("computing R[%d,%d] = " % (i, j), end=' ')
            uj = bs.bfs[j]
            J = uj.atom_index
            potj = ps.pots[J]

            ### DEBUG
            debug = 0
            if debug > 0:
                import matplotlib.pyplot as plt
                r = np.linspace(-5.0, 5.0, 100000)
                x = 0.0 * r
                y = 0.0 * r
                z = r
                plt.plot(r, potential(x, y, z), label=r"$V$")
                plt.plot(r, poti(x, y, z), label=r"$V_{%d}$" % (I + 1))
                plt.plot(r,
                         potj(x, y, z),
                         ls="--",
                         label=r"$V_{%d}$" % (J + 1))
                plt.plot(r, (potential(x, y, z) - poti(x, y, z)) *
                         (potential(x, y, z) - potj(x, y, z)),
                         ls="-.",
                         label=r"$(V-V_{%d})(V-V_{%d})$" % (I + 1, J + 1))
                VmVi = potential(x, y, z) - poti(x, y, z)
                xi, yi, zi = poti.center
                ri = np.sqrt((x - xi)**2 + (y - yi)**2 + (z - zi)**2)
                VmVi[ri < 2 * poti.rmin] = 0.0

                VmVj = potential(x, y, z) - potj(x, y, z)
                xj, yj, zj = potj.center
                rj = np.sqrt((x - xj)**2 + (y - yj)**2 + (z - zj)**2)
                VmVi[rj < 2 * potj.rmin] = 0.0

                plt.plot(r,
                         VmVi * VmVj,
                         ls="--",
                         label=r"$(V-V_{%d})(V-V_{%d})$ (outer region only)" %
                         (I + 1, J + 1))

                plt.ylim((-100.0, +100.0))
                plt.legend()
                plt.show()
            ###

            def integrand(x, y, z):
                V = potential(x, y, z)
                Vi = poti(x, y, z)
                Vj = potj(x, y, z)
                # Very close to each nucleus, the molecular Kohn-Sham potential
                # should be dominated by the nuclear attraction potential -Z/r.
                # In this region the atomic and molecular Kohn-Sham potentials should
                # cancel exactly, V-Vi = 0. However, subtracting two large numbers that
                # tend to infinity, will never give exactly 0 due to numerical errors.
                # Therefore a circle of radius 2*rmin is excised around atom i, where
                #  V-Vi is explicitly set to zero.
                VmVi = V - Vi
                xi, yi, zi = poti.center
                ri = np.sqrt((x - xi)**2 + (y - yi)**2 + (z - zi)**2)
                VmVi[ri < 2 * poti.rmin] = 0.0

                VmVj = V - Vj
                xj, yj, zj = potj.center
                rj = np.sqrt((x - xj)**2 + (y - yj)**2 + (z - zj)**2)
                VmVi[rj < 2 * potj.rmin] = 0.0

                # ui (V-Vi) (V-Vj) uj
                return ui(x, y, z) * VmVi * VmVj * uj(x, y, z)


#                return ui(x,y,z)*(V-Vi)*(V-Vj)*uj(x,y,z)

            R[i, j] = integral(atomlist, integrand)
            R[j, i] = R[i, j]

            print(R[i, j])

    return R
Esempio n. 5
0
def fvvm_matrix_elements(atomlist, bfs, potential, E, r0):
    """
    compute matrix elements of eqns. (8) and (9) in Ref. [1] which are
    needed for the finite-volume variational method. The integration
    is limited to a sphere of radius r0 around the origin.

    Parameters
    ==========
    atomlist     :  list of tuples (Z,[x,y,z]) with atomic numbers
                    and positions
    bfs          :  list of callables, `n` real basis functions 
    potential    :  callable, potential(x,y,z) evaluates effective potential
    E            :  float, energy of continuum orbital
    r0           :  float, radius of sphere which defines the integration volume

    Returns
    =======
    A            :  n x n matrix
    D            :  n x n matrix
    S            :  n x n matrix with overlap in finite volume
    """
    # number of basis functions
    nbfs = len(bfs)
    print " %d basis functions" % nbfs

    # volume integral
    #       __     __
    # A  = <\/chi |\/chi >   +  2 <chi |(V-E)|chi >
    #  ij        i      j V           i          j V
    A = np.zeros((nbfs, nbfs))
    #
    # surface integral
    #  D  = <chi |chi >
    #   ij      i    j surface of V
    D = np.zeros((nbfs, nbfs))
    #
    # overlap inside volume
    #
    # S   = <chi |chi >
    #  ij       i    j V
    S = np.zeros((nbfs, nbfs))

    # angular grid for surface integral
    Lmax, (th, ph,
           angular_weights) = select_angular_grid(settings.lebedev_order)
    # cartesian coordinates of Lebedev points on sphere of
    # radius r0
    xS = r0 * np.sin(th) * np.cos(ph)
    yS = r0 * np.sin(th) * np.sin(ph)
    zS = r0 * np.cos(th)

    for i in range(0, nbfs):
        # basis function i
        chi_i = bfs[i]
        for j in range(i, nbfs):
            # basis function j
            print " integrals between basis functions i= %d and j= %d" % (
                i + 1, j + 1)
            chi_j = bfs[j]

            # volume integral A_ij
            grad_prod_func = gradient_product(atomlist, chi_i, chi_j)

            def integrand(x, y, z):
                # (grad chi_i).(grad chi_j)
                integ = grad_prod_func(x, y, z)
                # 2 * chi_i * chi_j (V-E)
                integ += 2 * chi_i(x, y, z) * chi_j(
                    x, y, z) * (potential(x, y, z) - E)
                # Outside the integration volume the integrand
                # is set to zero.
                r = np.sqrt(x * x + y * y + z * z)
                integ[r0 < r] = 0.0
                return integ

            A[i, j] = integral(atomlist, integrand)
            # A is symmetric
            A[j, i] = A[i, j]

            # surface integral D_ij
            D[i, j] = 4.0 * np.pi * r0**2 * np.sum(
                angular_weights * chi_i(xS, yS, zS) * chi_j(xS, yS, zS))
            # D is symmetric
            D[j, i] = D[i, j]

            # overlap integral S_ij inside the volume
            def integrand(x, y, z):
                integ = chi_i(x, y, z) * chi_j(x, y, z)
                # Outside the integration volume the integrand
                # is set to zero.
                r = np.sqrt(x * x + y * y + z * z)
                integ[r0 < r] = 0.0
                return integ

            S[i, j] = integral(atomlist, integrand)
            # S is symmetric
            S[j, i] = S[i, j]

    return A, D, S
Esempio n. 6
0
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()