Jellium

In this tutorial, we try to reproduce some old jellium calculations by Lang and Kohn [Lang70].

Bulk

Let’s do a calculation for \(r_s=5\) Bohr. We use a cubic cell with a lattice constant of \(a=1.6\) Å, 8*8*8 grid points and and a k-point sampling of 12*12*12 points.

import numpy as np
from ase import Atoms
from ase.units import Bohr
from gpaw.jellium import Jellium
from gpaw import GPAW, PW

rs = 5.0 * Bohr  # Wigner-Seitz radius
h = 0.2          # grid-spacing
a = 8 * h        # lattice constant
k = 12           # number of k-points (k*k*k)

ne = a**3 / (4 * np.pi / 3 * rs**3)
jellium = Jellium(ne)

bulk = Atoms(pbc=True, cell=(a, a, a))
bulk.calc = GPAW(mode=PW(400.0),
                 background_charge=jellium,
                 xc='LDA_X+LDA_C_WIGNER',
                 nbands=5,
                 kpts=[k, k, k],
                 h=h,
                 txt='bulk.txt')
e0 = bulk.get_potential_energy()

In the text output from the calculation, one can see that the cell contains 0.0527907 electrons.

Surfaces

Now, we will do a surface calculation. We put a slab of thickness 16.0 Å inside a box, periodically repeated in the x- and y-directions only, of size 1.6*1.6*25.6 Å and use 12*12 k-points to sample the surface BZ:

import numpy as np
from ase import Atoms
from ase.units import Bohr
from gpaw.jellium import JelliumSlab
from gpaw import GPAW, PW

rs = 5.0 * Bohr  # Wigner-Seitz radius
h = 0.2          # grid-spacing
a = 8 * h        # lattice constant
v = 3 * a        # vacuum
L = 10 * a       # thickness
k = 12           # number of k-points (k*k*1)

ne = a**2 * L / (4 * np.pi / 3 * rs**3)

eps = 0.001  # displace surfaces away from grid-points
jellium = JelliumSlab(ne, z1=v - eps, z2=v + L + eps)

surf = Atoms(pbc=(True, True, False),
             cell=(a, a, v + L + v))
surf.calc = GPAW(mode=PW(400.0),
                 background_charge=jellium,
                 xc='LDA_X+LDA_C_WIGNER',
                 eigensolver='dav',
                 kpts=[k, k, 1],
                 h=h,
                 convergence={'density': 0.001},
                 nbands=int(ne / 2) + 15,
                 txt='surface.txt')
e = surf.get_potential_energy()
surf.calc.write('surface.gpw')

The surface energy is:

>>> from ase.io import read
>>> e0 = read('bulk.txt').get_potential_energy()
>>> e = read('surface.txt').get_potential_energy()
>>> a = 1.6
>>> L = 10 * a
>>> sigma = (e - L / a * e0) / 2 / a**2
>>> print('%.2f mev/Ang^2' % (1000 * sigma))
5.39 mev/Ang^2
>>> print('%.1f erg/cm^2' % (sigma / 6.24150974e-5))
86.4 erg/cm^2

which is reasonably close to the value from Lang and Kohn: 100 ergs/cm\(^2\).

Here is the electron density profile:

# web-page: fig2.png
import numpy as np
import matplotlib.pyplot as plt
from ase.units import Bohr
from gpaw import GPAW
rs = 5.0 * Bohr
calc = GPAW('surface.gpw', txt=None)
density = calc.get_pseudo_density()[0, 0]
h = 0.2
a = 8 * h
v = 3 * a
L = 10 * a
z = np.linspace(0, v + L + v, len(density), endpoint=False)
# Position of surface is between two grid points:
z0 = (v + L - h / 2)
n = 1 / (4 * np.pi / 3 * rs**3)  # electron density
kF = (3 * np.pi**2 * n)**(1.0 / 3)
lambdaF = 2 * np.pi / kF  # Fermi wavelength
plt.figure(figsize=(6, 6 / 2**0.5))
plt.plot([-L / lambdaF, -L / lambdaF, 0, 0], [0, 1, 1, 0], 'k')
plt.plot((z - z0) / lambdaF, density / n)
# plt.xlim(xmin=-1.2, xmax=1)
plt.ylim(ymin=0)
plt.title(r'$r_s=%.1f\ \mathrm{Bohr},\ \lambda_F=%.1f\ \mathrm{Bohr}$' %
          (rs / Bohr, lambdaF / Bohr))
plt.xlabel('DISTANCE (FERMI WAVELENGTHS)')
plt.ylabel('ELECTRON DENSITY')
plt.savefig('fig2.png')

Compare with Fig. 2 in [Lang70]:

../../../_images/fig2.png

Other jellium geometries

For other geometries, one will have to subclass Jellium, and implement the get_mask() method:

class gpaw.jellium.Jellium(charge)[source]

The Jellium object

Initialize the Jellium object

Input: charge, the total Jellium background charge.

add_charge_to(rhot_g)[source]

Add Jellium background charge to pseudo charge density rhot_g

get_mask()[source]

Choose which grid points are inside the jellium.

gd: grid descriptor

Return ndarray of ones and zeros indicating where the jellium is. This implementation will put the positive background in the whole cell. Overwrite this method in subclasses.

set_grid_descriptor(gd)[source]

Set the grid descriptor for the Jellium background charge

The JelliumSlab is one such example of a subclass of gpaw.jellium.Jellium:

class gpaw.jellium.JelliumSlab(charge, z1, z2)[source]

The Jellium slab object

Put the positive background charge where z1 < z < z2.

z1: float

Position of lower surface in Angstrom units.

z2: float

Position of upper surface in Angstrom units.

get_mask()[source]

Choose which grid points are inside the jellium.

gd: grid descriptor

Return ndarray of ones and zeros indicating where the jellium is. This implementation will put the positive background in the whole cell. Overwrite this method in subclasses.


[Lang70] (1,2)

N. D. Lang and W. Kohn, Phys. Rev. B 1, 4555-4568 (1970), Theory of Metal Surfaces: Charge Density and Surface Energy, doi: 10.1103/PhysRevB.1.4555