Linear response TDDFT
Ground state
The linear response TDDFT calculation needs a converged ground state calculation with a set of unoccupied states. Note, that the eigensolver ‘rmm-diis’ should not be used for the calculation of unoccupied states, better use ‘dav’ or ‘cg’:
from ase import Atoms
from gpaw import GPAW
# Beryllium atom
atoms = Atoms(symbols='Be',
positions=[(0, 0, 0)],
pbc=False)
# Add 4.0 ang vacuum around the atom
atoms.center(vacuum=4.0)
# Create GPAW calculator
calc = GPAW(mode='fd', nbands=10, h=0.3)
# Attach calculator to atoms
atoms.calc = calc
# Calculate the ground state
energy = atoms.get_potential_energy()
# converge also the empty states (the density is converged already)
calc = calc.fixed_density(
convergence={'bands': 8})
# Save the ground state
calc.write('Be_gs_8bands.gpw', 'all')
Calculating the Omega Matrix
The next step is to calculate the Omega Matrix from the ground state orbitals:
from gpaw import GPAW
from gpaw.lrtddft import LrTDDFT
c = GPAW('Be_gs_8bands.gpw')
istart = 0 # band index of the first occ. band to consider
jend = 8 # band index of the last unocc. band to consider
lr = LrTDDFT(c, xc='LDA', restrict={'istart': istart, 'jend': jend},
nspins=2) # force the calculation of triplet excitations also
lr.write('lr.dat.gz')
alternatively one can also restrict the number of transitions by their energy:
from gpaw import GPAW
from gpaw.lrtddft import LrTDDFT
c = GPAW('Be_gs_8bands.gpw')
dE = 10 # maximal Kohn-Sham transition energy to consider in eV
lr = LrTDDFT(c, xc='LDA', restrict={'energy_range': dE})
lr.write('lr_dE.dat.gz')
Note, that parallelization over spin does not work here. As a workaround,
domain decomposition only (parallel={'domain': world.size}
,
see Domain decomposition)
has to be used for spin polarised
calculations in parallel.
Extracting the spectrum
The dipole spectrum can be evaluated from the Omega matrix and written to a file:
from gpaw.lrtddft import LrTDDFT
from gpaw.lrtddft import photoabsorption_spectrum
lr = LrTDDFT.read('lr.dat.gz')
lr.diagonalize()
# write the spectrum to the data file
photoabsorption_spectrum(lr,
'spectrum_w.05eV.dat', # data file name
width=0.05) # width in eV
The spectrum may be also extracted and plotted in energy or wavelength directly:
import matplotlib.pyplot as plt
from gpaw.lrtddft import LrTDDFT
from gpaw.lrtddft.spectrum import get_folded_spectrum
lr = LrTDDFT.read('lr.dat.gz')
plt.subplot(121)
# spectrum in energy
x, y = get_folded_spectrum(lr, width=0.05)
plt.plot(x, y[:, 0])
plt.xlabel('energy [eV]')
plt.ylabel('folded oscillator strength')
plt.subplot(122)
# spectrum in wavelengths
x, y = get_folded_spectrum(lr, energyunit='nm', width=10)
plt.plot(x, y[:, 0])
plt.xlabel('wavelength [nm]')
plt.show()
Testing convergence
You can test the convergence of the Kohn-Sham transition basis size by restricting the basis in the diagonalisation step, e.g.:
from gpaw.lrtddft import LrTDDFT
lr = LrTDDFT.read('lr.dat.gz')
lr.diagonalize(restrict={'energy_range':6.})
This can be automated by using the check_convergence function:
from gpaw.lrtddft import LrTDDFT
from gpaw.lrtddft.convergence import check_convergence
lr = LrTDDFT.read('lr.dat.gz')
check_convergence(lr,
'linear_response',
'my plot title',
dE=.2,
emax=6.)
which will create a directory ‘linear_response’. In this directory there will be a file ‘conv.gpl’ for gnuplot that compares the spectra varying the basis size.
Analysing the transitions
The single transitions (or a list of transitions) can be analysed as follows (output printed):
from gpaw.lrtddft import LrTDDFT
lr = LrTDDFT.read('lr.dat.gz')
lr.diagonalize()
# analyse transition 1
lr.analyse(1)
# analyse transition 0-10
lr.analyse(range(11))
Relaxation in the excited state
Despite that we do not have analytical gradients in linear response TDDFT, we may use finite differences for relaxation in the excited state. This example shows how to relax the sodium dimer in the B excited state:
from ase import Atoms, io, optimize
from gpaw import GPAW, FermiDirac
from gpaw.utilities.adjust_cell import adjust_cell
from gpaw.lrtddft import LrTDDFT
from gpaw.lrtddft.excited_state import ExcitedState
box = 5. # box dimension
h = 0.25 # grid spacing
width = 0.01 # Fermi width
nbands = 6 # bands in GS calculation
nconv = 4 # bands in GS calculation to converge
R = 2.99 # starting distance
iex = 1 # excited state index
d = 0.01 # step for numerical force evaluation
exc = 'LDA' # xc for the linear response TDDFT kernel
parallel = 4 # number of independent parrallel workers for force evaluation
s = Atoms('Na2', [[0, 0, 0], [0, 0, R]])
adjust_cell(s, box, h=h)
c = GPAW(mode='fd', h=h, nbands=nbands, eigensolver='cg',
occupations=FermiDirac(width=width),
setups={'Na': '1'},
convergence={'bands': nconv})
c.calculate(s)
lr = LrTDDFT(c, xc=exc, restrict={'eps': 0.1, 'jend': nconv - 1})
ex = ExcitedState(lr, iex, d=d, parallel=parallel)
s.calc = ex
ftraj = 'relax_ex' + str(iex)
ftraj += '_box' + str(box) + '_h' + str(h)
ftraj += '_d' + str(d) + '.traj'
traj = io.Trajectory(ftraj, 'w', s)
dyn = optimize.FIRE(s)
dyn.attach(traj.write)
dyn.run(fmax=0.05)
The example runs on a single core. If started on 8 cores, it will split
world
into 4 independent parts (2 cores each) that can calculate
4 displacements in parallel at the same time.
Quick reference
Parameters for LrTDDFT:
keyword |
type |
default value |
description |
---|---|---|---|
|
|
Calculator object of ground state calculation |
|
|
|
1 |
number of excited state spins, i.e.
singlet-triplet transitions are
calculated with |
|
|
xc of calculator |
Exchange-correlation for LrTDDFT, can differ from ground state value. Use “RPA” for the random phase approximation. |
|
|
{} |
Restrictions collected as dict, see below |
The keyword restrict
can also be used to restrict the already pre-calculated
linear response TDDFT object as:
from gpaw.lrtddft import LrTDDFT
lr = LrTDDFT.read('lr.dat.gz')
lr.diagonalize()
print('Full matrix gives', len(lr), 'excitations')
# now diagonalize using a selectron of orbitals
restrict = {'energy_range': 5} # eV
lr.diagonalize(restrict=restrict)
print('Restriction by', restrict, 'gives', len(lr), 'excitation')
restrict = {'to': [3, 4]}
lr.diagonalize(restrict=restrict)
print('Restriction by', restrict, 'gives', len(lr), 'excitations')
The parameters of restrict
are:
keyword |
type |
default value |
description |
---|---|---|---|
|
|
0.001 |
Minimal occupation difference for a transition |
|
|
0 |
first occupied state to consider |
|
|
number of bands |
last unoccupied state to consider |
|
|
None |
Energy range to consider in the involved
Kohn-Sham orbitals (replaces |
|
|
None |
List of indices allowed for occupied Kohn-Sham orbitals |
|
|
None |
List of indices allowed for unoccupied Kohn-Sham orbitals |