Beispiel #1
0
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()
Beispiel #2
0
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()
Beispiel #3
0
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 ""
Beispiel #4
0
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()
Beispiel #5
0
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()
Beispiel #6
0
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
Beispiel #7
0
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
Beispiel #8
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()