Tamm–Dancoff approximation

Tamm–Dancoff approximation#

The Tamm–Dancoff approximation (TDA) corresponds to setting the off-diagonal \({B}\)-block in the electronic Hessian \({E}^{[2]}\) to zero and diagonalizing the \({A}\)-block. This leads to computational savings and gives a concrete configuration interaction singles (CIS) representation of the excited states. Moreover, it has typically a small impact on spectra and is therefore justifiable.

We will illustrate this approximation with a study of the lowest excited states in ethylene.

Hide code cell source
import numpy as np
import py3Dmol as p3d
import veloxchem as vlx
Hide code cell source
ethene_xyz = """6

C        0.67759997    0.00000000    0.00000000
C       -0.67759997    0.00000000    0.00000000
H        1.21655197    0.92414474    0.00000000
H        1.21655197   -0.92414474    0.00000000
H       -1.21655197   -0.92414474    0.00000000
H       -1.21655197    0.92414474    0.00000000
"""

You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension:
jupyter labextension install jupyterlab_3dmol

First, we optimize the SCF reference state.

molecule = vlx.Molecule.from_xyz_string(ethene_xyz)
basis = vlx.MolecularBasis.read(molecule, "6-31g", ostream=None)
scf_drv = vlx.ScfRestrictedDriver()
scf_drv.ostream.mute()

scf_drv.xcfun = "b3lyp"

scf_results = scf_drv.compute(molecule, basis)

The matrix size in the TDA eigenvalue equation is given by the number of one-electron excitations.

norb = basis.get_dimension_of_basis(molecule)
nocc = molecule.number_of_alpha_electrons()
nvirt = norb - nocc

n = nocc * nvirt

print("Number of occupied orbitals:", nocc)
print("Number of unoccupied orbitals:", nvirt)
print("Number of excitations:", n)
Number of occupied orbitals: 8
Number of unoccupied orbitals: 18
Number of excitations: 144

Second, we retrieve the \(A\)-block of the \(E^{[2]}\)-matrix.

lres_drv = vlx.LinearResponseEigenSolver()
lres_drv.ostream.mute()

_ = lres_drv.compute(molecule, basis, scf_results)
E2 = lres_drv.get_e2(molecule, basis, scf_results)

A = E2[:n, :n]

Third, we perform a diagonalization of matrix \(A\) to obtain the excitation energies. The excitation energies of the five lowest states are printed out.

eigs, X = np.linalg.eigh(A)

print("Excitation energies (eV):\n", eigs[:5] * 27.2114)
Excitation energies (eV):
 [8.57868869 9.00818369 9.22485575 9.60189688 9.76762482]

Reference calculation#

Spectra based on the TDA approach can also be obtained with the TDAExciDriver class. We perform a reference calculation to confirm our results.

tda_drv = vlx.TDAExciDriver()
tda_drv.ostream.mute()

tda_drv.nstates = 5

tda_results = tda_drv.compute(molecule, basis, scf_results)
print("Excitation energies (eV):\n", tda_results["eigenvalues"] * 27.2114)
Excitation energies (eV):
 [8.57868811 9.00818379 9.22485652 9.60189671 9.76762549]

We note that these results are in perfect agreement with those obtained above.