""".. _wannier tutorial:
============================================
Partly occupied Wannier Functions
============================================
This tutorial walks through building **partly occupied Wannier
functions** with the :mod:`ase.dft.wannier` module
and the `GPAW `_ electronic structure code.
For more information on the details of the method and the implementation, see
| K. S. Thygesen, L. B. Hansen, and K. W. Jacobsen
| Partly occupied Wannier functions: Construction and applications
| `Phys. Rev. B 72, 125119 (2005) `__
.. contents:: **Outline**
:depth: 2
:local:
Benzene molecule
================
Step 1 – Ground-state calculation
---------------------------------
Run the script below to obtain the ground-state density and the
Kohn–Sham (KS) orbitals. The result is stored in :file:`benzene.gpw`.
"""
# %%
import matplotlib.pyplot as plt
import numpy as np
from gpaw import GPAW, restart
from ase import Atoms
from ase.build import molecule
from ase.dft.kpoints import monkhorst_pack
from ase.dft.wannier import Wannier
atoms = molecule('C6H6')
atoms.center(vacuum=3.5)
calc = GPAW(mode='pw', xc='PBE', txt='benzene.txt', nbands=18)
atoms.calc = calc
atoms.get_potential_energy()
calc = calc.fixed_density(
txt='benzene-harris.txt',
nbands=40,
convergence={'bands': 35},
)
atoms.get_potential_energy()
calc.write('benzene.gpw', mode='all')
# %%
# Step 2 – Maximally localized WFs for the occupied subspace (15 WFs)
# -------------------------------------------------------------------
#
# There are 15 occupied bands in the benzene molecule. We construct one
# Wannier function per occupied band by setting ``nwannier = 15``.
# By calling ``wan.localize()``, the code attempts to minimize the spread
# functional using a gradient-descent algorithm.
# The resulting WFs are written to .cube files, which allows them
# to be inspected using e.g. VESTA.
# %%
atoms, calc = restart('benzene.gpw', txt=None)
# Make wannier functions of occupied space only
wan = Wannier(nwannier=15, calc=calc)
wan.localize()
for i in range(wan.nwannier):
wan.write_cube(i, f'benzene15_{i}.cube')
# %%
# Step 3 – Adding three extra degrees of freedom (18 WFs)
# -------------------------------------------------------
#
# To improve localization we augment the basis with three extra
# Wannier functions - so-called *extra degrees of freedom*
# (``nwannier = 18``, ``fixedstates = 15``).
# This will allow the Wannierization procedure to use the unoccupied states to
# minimize spread functional.
# %%
atoms, calc = restart('benzene.gpw', txt=None)
# Make wannier functions using (three) extra degrees of freedom.
wan = Wannier(nwannier=18, calc=calc, fixedstates=15)
wan.localize()
wan.save('wan18.json')
for i in range(wan.nwannier):
wan.write_cube(i, f'benzene18_{i}.cube')
# %%
# Step 4 – Spectral-weight analysis
# ---------------------------------
#
# The script below projects the WFs on the KS eigenstates. You should see
# the 15 lowest bands perfectly reconstructed (weight ≃ 1.0) while higher
# bands are only partially represented.
# %%
atoms, calc = restart('benzene.gpw', txt=None)
wan = Wannier(nwannier=18, calc=calc, fixedstates=15, file='wan18.json')
weight_n = np.sum(abs(wan.V_knw[0]) ** 2, 1)
N = len(weight_n)
F = wan.fixedstates_k[0]
plt.figure(1, figsize=(12, 4))
plt.bar(
range(1, N + 1),
weight_n,
width=0.65,
bottom=0,
color='k',
edgecolor='k',
linewidth=None,
align='center',
orientation='vertical',
)
plt.plot([F + 0.5, F + 0.5], [0, 1], 'k--')
plt.axis(xmin=0.32, xmax=N + 1.33, ymin=0, ymax=1)
plt.xlabel('Eigenstate')
plt.ylabel('Projection of wannier functions')
plt.savefig('spectral_weight.png')
plt.show()
# %%
# Polyacetylene chain (1-D periodic)
# ==================================
#
# We now want to construct partially occupied Wannier functions
# to describe a polyacetylene chain.
#
# Step 1 – Structure & ground-state calculation
# ---------------------------------------------
#
# Polyacetylene is modelled as an infinite chain; we therefore enable
# periodic boundary conditions along *x*.
# %%
kpts = monkhorst_pack((13, 1, 1))
calc = GPAW(
mode='pw',
xc='PBE',
kpts=kpts,
nbands=12,
txt='poly.txt',
convergence={'bands': 9},
symmetry='off',
)
CC = 1.38
CH = 1.094
a = 2.45
x = a / 2.0
y = np.sqrt(CC**2 - x**2)
atoms = Atoms(
'C2H2',
pbc=(True, False, False),
cell=(a, 8.0, 6.0),
calculator=calc,
positions=[[0, 0, 0], [x, y, 0], [x, y + CH, 0], [0, -CH, 0]],
)
atoms.center()
atoms.get_potential_energy()
calc.write('poly.gpw', mode='all')
# %%
# Step 2 – Wannierization
# -----------------------
#
# We repeat the localization procedure, keeping the five lowest
# bands fixed and adding one extra degree of freedom to aid localization.
# %%
import numpy as np
from gpaw import restart
from ase.dft.wannier import Wannier
atoms, calc = restart('poly.gpw', txt=None)
# Make wannier functions using (one) extra degree of freedom
wan = Wannier(
nwannier=6,
calc=calc,
fixedenergy=1.5,
initialwannier='orbitals',
functional='var',
)
wan.localize()
wan.save('poly.json')
wan.translate_all_to_cell((2, 0, 0))
for i in range(wan.nwannier):
wan.write_cube(i, f'polyacetylene_{i}.cube')
# Print Kohn-Sham bandstructure
ef = calc.get_fermi_level()
with open('KSbands.txt', 'w') as fd:
for k, kpt_c in enumerate(calc.get_ibz_k_points()):
for eps in calc.get_eigenvalues(kpt=k):
print(kpt_c[0], eps - ef, file=fd)
# Print Wannier bandstructure
with open('WANbands.txt', 'w') as fd:
for k in np.linspace(-0.5, 0.5, 100):
ham = wan.get_hamiltonian_kpoint([k, 0, 0])
for eps in np.linalg.eigvalsh(ham).real:
print(k, eps - ef, file=fd)
# %%
# Step 3 – High-resolution band structure
# ---------------------------------------
#
# Using the Wannier Hamiltonian we can interpolate the band structure on a
# fine 100-point *k* mesh and compare it to the original DFT result.
# %%
fig = plt.figure(dpi=80, figsize=(4.2, 6))
fig.subplots_adjust(left=0.16, right=0.97, top=0.97, bottom=0.05)
# Plot KS bands
k, eps = np.loadtxt('KSbands.txt', unpack=True)
plt.plot(k, eps, 'ro', label='DFT', ms=9)
# Plot Wannier bands
k, eps = np.loadtxt('WANbands.txt', unpack=True)
plt.plot(k, eps, 'k.', label='Wannier')
plt.plot([-0.5, 0.5], [1, 1], 'k:', label='_nolegend_')
plt.text(-0.5, 1, 'fixedenergy', ha='left', va='bottom')
plt.axis('tight')
plt.xticks(
[-0.5, -0.25, 0, 0.25, 0.5],
[r'$X$', r'$\Delta$', r'$\Gamma$', r'$\Delta$', r'$X$'],
size=16,
)
plt.ylabel(r'$E - E_F\ \rm{(eV)}$', size=16)
plt.legend()
plt.savefig('bands.png', dpi=80)
plt.show()
# %%
# Within the fixed-energy window—that is, for energies below the fixed-energy
# line—the Wannier-interpolated bands coincide perfectly with the DFT reference
# (red circles). Above this window the match is lost, because the degrees of
# freedom deliberately mix several Kohn–Sham states to achieve maximal
# real-space localisation.