Example: Vibration resolved electronic spectrum: plams

Download GOFREQ_LR-TDDFTB_anthracene_S0S1fcf.run

#!/bin/sh

cp $TEST_DIRECTORY/GOFREQ_LR-TDDFTB_anthracene_S0S1fcf.plms .
cp $TEST_DIRECTORY/anthracene.xyz .

$AMSBIN/plams GOFREQ_LR-TDDFTB_anthracene_S0S1fcf.plms

Download GOFREQ_LR-TDDFTB_anthracene_S0S1fcf.plms

import sys
import numpy as np

config.log.stdout = 0

# This test calculates the vibrational fine structure of the S_0 -> S_1 transition in anthracene.

molfile = "anthracene.xyz"
excit = 1

# Common settings for all DFTB calculations:
comin = Settings()
comin.input.DFTB.resourcesdir = "DFTB.org/3ob-freq-1-2"
comin.input.DFTB.model = "DFTB3"


# ========= auxilliary functions ==================================================================
def get_total_energy(results):
    nprop = results.readrkf("Properties", "nEntries", file="dftb")
    for i in range(1, nprop + 1):
        if results.readrkf("Properties", "Subtype(%i)" % i, file="dftb").strip() == "DFTB Final Energy":
            return results.readrkf("Properties", "Value(%i)" % i, file="dftb")
    return None


def get_zero_point_energy(results):
    freqs = results.readrkf("Vibrations", "Frequencies[cm-1]", file="dftb")
    if isinstance(freqs, list):
        return Units.convert(0.5 * sum(freqs), "cm^-1", "Hartree")
    else:
        return Units.convert(0.5 * freqs, "cm^-1", "Hartree")


def extract_spectrum(fcf_results):
    return np.array(fcf_results.readkf("Fcf", "spectrum")).reshape(2, -1).transpose()


# ========= STEP 1: Ground state ==================================================================

# Optimize ground state geometry:
gs_mol_unoptimized = Molecule(filename=molfile)
gs_go = AMSJob(name="gs_go", molecule=gs_mol_unoptimized, settings=comin)
gs_go.settings.input.ams.Task = "GeometryOptimization"
gs_go.settings.input.ams.GeometryOptimization.convergence = "Gradients=1.0e-5"
gs_go_results = gs_go.run()
if not gs_go.check():
    print("ERROR: Ground state optimization crashed")
    sys.exit(1)
if gs_go_results.grep_output("Optimization Did Not Converge"):
    print("ERROR: Ground state optimization did not converge")
    sys.exit(1)
gs_mol_optimized = gs_go_results.get_molecule("Molecule")

# Calculate frequencies and normal modes of the ground state:
gs_freq = AMSJob(name="gs_freq", molecule=gs_mol_optimized, settings=comin)
gs_freq.settings.input.ams.Task = "SinglePoint"
gs_freq.settings.input.ams.properties.NormalModes = "true"
gs_freq.settings.input.ams.NumericalDifferentiation.Parallel.nCoresPerGroup = 1
gs_freq_results = gs_freq.run()
if not gs_freq.check():
    print("ERROR: Ground state frequency calculation crashed")
    sys.exit(1)

# Calculate vertical excitations:
gs_excit = AMSJob(name="gs_excit", molecule=gs_mol_optimized, settings=comin)
gs_excit.settings.input.ams.Task = "SinglePoint"
gs_excit.settings.input.DFTB.properties.excitations.tddftb.calc = "singlet"
gs_excit.settings.input.DFTB.properties.excitations.tddftb.lowest = excit + 9
gs_excit.settings.input.DFTB.properties.excitations.tddftb["print"] = "evcontribs"
gs_excit_results = gs_excit.run()
if not gs_excit.check():
    print("ERROR: Ground state excitations calculation crashed")
    sys.exit(1)

# Print ground state energies:
print("Energies in the ground state equilibrium geometry:")
E_DFTB_RGS = get_total_energy(gs_excit_results)
E_ZPE_RGS = get_zero_point_energy(gs_freq_results)
Delta_RGS = gs_excit_results.readrkf("Excitations SS A", "excenergies", file="dftb")[excit - 1]
E_GS = E_DFTB_RGS + E_ZPE_RGS
print("  E_DFTB(R_GS) = %f eV" % (Units.convert(E_DFTB_RGS, "Hartree", "eV")))
print("   E_ZPE(R_GS) = %f eV" % (Units.convert(E_ZPE_RGS, "Hartree", "eV")))
print("         E_GS  = %f eV" % (Units.convert(E_GS, "Hartree", "eV")))


# ========= STEP 2: Excited state =================================================================

# Optimize the excited state geometry:
ex_go = AMSJob(name="ex_go", molecule=gs_mol_optimized, settings=comin)
ex_go.settings.input.ams.Task = "GeometryOptimization"
ex_go.settings.input.ams.GeometryOptimization.convergence = "Gradients=1.0e-5"
ex_go.settings.input.DFTB.properties.excitations.tddftb.calc = "singlet"
ex_go.settings.input.DFTB.properties.excitations.tddftb.lowest = excit
ex_go.settings.input.DFTB.properties.excitations.tddftb["print"] = "evcontribs"
ex_go.settings.input.DFTB.properties.excitations.tddftbgradients.excitation = excit
ex_go.settings.input.DFTB.properties.excitations.tddftbgradients.eigenfollow = "true"
ex_go.settings.input.ams.log.info = "TDDFTBExcitationFollowerModule"
ex_go_results = ex_go.run()
if not ex_go.check():
    print("ERROR: Excited state optimization crashed")
    sys.exit(1)
if ex_go_results.grep_output("Optimization Did Not Converge"):
    print("ERROR: Excited state optimization did not converge")
    sys.exit(1)
ex_mol_optimized = ex_go_results.get_molecule("Molecule")

# Check if the potential energy surface was switched during the optimization:
# (This happens if the optimizer goes through a conical intersection.)
PES_switches = ex_go_results.grep_file("ams.log", "TD-DFTB Eigenfollower switching PES:")
if PES_switches:
    newexcit = int(PES_switches[-1].split()[-1])
    print("PES switched during EXGO!!! %i -> %i" % (excit, newexcit))
else:
    newexcit = excit

# Calculate frequencies and normal modes of the excited state:
ex_freq = AMSJob(name="ex_freq", molecule=ex_mol_optimized, settings=comin)
ex_freq.settings.input.ams.Task = "SinglePoint"
ex_freq.settings.input.ams.properties.NormalModes = "true"
ex_freq.settings.input.ams.NumericalDifferentiation.Parallel.nCoresPerGroup = 1
ex_freq.settings.input.DFTB.properties.excitations.tddftb.calc = "singlet"
ex_freq.settings.input.DFTB.properties.excitations.tddftb.lowest = newexcit
ex_freq.settings.input.DFTB.properties.excitations.tddftb["print"] = "evcontribs"
ex_freq.settings.input.DFTB.properties.excitations.tddftbgradients.excitation = newexcit
ex_freq_results = ex_freq.run()
if not ex_freq.check():
    print("ERROR: Excited state frequency calculation crashed")
    sys.exit(1)

# Calculate vertical excitations in excited state geometry:
ex_excit = AMSJob(name="ex_excit", molecule=ex_mol_optimized, settings=comin)
ex_excit.settings.input.ams.Task = "SinglePoint"
ex_excit.settings.input.DFTB.properties.excitations.tddftb.calc = "singlet"
ex_excit.settings.input.DFTB.properties.excitations.tddftb.lowest = newexcit + 9
ex_excit.settings.input.DFTB.properties.excitations.tddftb["print"] = "evcontribs"
ex_excit_results = ex_excit.run()
if not ex_excit.check():
    print("ERROR: Excited state geometry excitations calculation crashed")
    sys.exit(1)

# Print excited state energies:
print("Energies in the excited state equilibrium geometry:")
E_DFTB_REX = get_total_energy(ex_excit_results)
E_ZPE_REX = get_zero_point_energy(ex_freq_results)
Delta_REX = ex_excit_results.readrkf("Excitations SS A", "excenergies", file="dftb")[excit - 1]
E_EX = E_DFTB_REX + E_ZPE_REX + Delta_REX
print("  E_DFTB(R_EX) = %f eV" % (Units.convert(E_DFTB_REX, "Hartree", "eV")))
print("   E_ZPE(R_EX) = %f eV" % (Units.convert(E_ZPE_REX, "Hartree", "eV")))
print("   Delta(R_EX) = %f eV" % (Units.convert(Delta_REX, "Hartree", "eV")))
print("         E_EX  = %f eV" % (Units.convert(E_EX, "Hartree", "eV")))

# Print excitation energies:
print("Excitation energies:")
print("   Delta(R_GS) = %f eV" % (Units.convert(Delta_RGS, "Hartree", "eV")))
print("         E_0-0 = %f eV" % (Units.convert(E_EX - E_GS, "Hartree", "eV")))
print("          Diff = %f eV" % (Units.convert(Delta_RGS - (E_EX - E_GS), "Hartree", "eV")))


# ========= STEP 3: Vibrational fine structure with the FCF program ===============================

# Settings for the FCF program
fcfin = Settings()
fcfin.input.spectrum.spcmin = "0.0"
fcfin.input.spectrum.spcmax = "5000.0"
fcfin.input.spectrum.spclen = "501"
fcfin.input.spectrum.lineshape = "Stick"
fcfin.input.numericalquality = "Basic"
fcfin.input.translate = True
fcfin.input.rotate = True

# Calculate vibrational fine structure
fcf = FCFJob(
    name="fcf",
    settings=fcfin,
    inputjob1=gs_freq_results.rkfpath(file="dftb"),
    inputjob2=ex_freq_results.rkfpath(file="dftb"),
)
fcf_results = fcf.run()
if not fcf.check():
    print("ERROR: FCF calculation failed")
    sys.exit(1)

# Extract and print the spectrum:
spectrum = extract_spectrum(fcf_results)
np.set_printoptions(formatter={"float": " {: 0.8f} ".format}, threshold=1e6)
print("Vibrational fine structure:")
print("Energy [cm^-1]    Intensity")
print(spectrum)