Vibronic Density of States with ADF

This example uses ADF to model the density of states involved in the charge transfer between two systems using the AH-FC method - in this case an NO2 radical and an NO2 anion.

To follow along, either

Worked Example

Initial Imports

Import PLAMS components and set up to run jobs in tandem.

from scm.plams import Settings, AMSJob, Molecule, Atom, FCFJob, config, JobRunner
from scm.plams.recipes.fcf_dos import FCFDOS
config.default_jobrunner.parallel = JobRunner(parallel=True, maxjobs=2)

Setup Molecules

Create the NO2 molecules using pre-optimized geometries (usually the geometry optimization step would come first).

no2_radical = Molecule()
no2_radical.add_atom(Atom(atnum=7, coords=(0.0, 0.0, -0.01857566)))
no2_radical.add_atom(Atom(atnum=8, coords=(0.0, 1.09915770, -0.49171967)))
no2_radical.add_atom(Atom(atnum=8, coords=(0.0, -1.09915770, -0.49171967)))

no2_anion = Molecule()
no2_anion.add_atom(Atom(atnum=7, coords=(0.0, 0.0, 0.12041)))
no2_anion.add_atom(Atom(atnum=8, coords=(0.0, 1.070642, -0.555172)))
no2_anion.add_atom(Atom(atnum=8, coords=(0.0, -1.070642, -0.555172)))

Calculate Vibrational Frequencies

Create the settings objects for the ADF donor/acceptor vibrational frequencies calculations. Run the calculations in parallel.

settings_freq = Settings()
settings_freq.input.adf.symmetry = "NoSym"
settings_freq.input.adf.basis.type = "DZP"
settings_freq.input.adf.basis.core = "None"
settings_freq.input.adf.xc.lda = "SCF VWN"
settings_freq.input.ams.Task = "SinglePoint"
settings_freq.input.ams.Properties.NormalModes = "Yes"
settings_freq.input.adf.title = "Vibrational frequencies"

settings_freq_radical = settings_freq.copy()
settings_freq_radical.input.ams.system.charge = 0
settings_freq_radical.input.adf.spinpolarization = 1
settings_freq_radical.input.adf.unrestricted = "Yes"

settings_freq_anion = settings_freq.copy()
settings_freq_anion.input.ams.system.charge = -1
freq_job_radical = AMSJob(molecule=no2_radical, settings=settings_freq_radical, name="fsradical")
freq_job_anion = AMSJob(molecule=no2_anion, settings=settings_freq_anion, name="fsanion")

freq_results = (freq_job_radical.run(), freq_job_anion.run())
[01.11|12:28:57] JOB fsradical STARTED
[01.11|12:28:57] JOB fsanion STARTED
[01.11|12:28:57] JOB fsanion RUNNING
[01.11|12:28:57] JOB fsradical RUNNING
[01.11|12:29:10] JOB fsanion FINISHED
[01.11|12:29:10] JOB fsanion SUCCESSFUL
[01.11|12:29:12] JOB fsradical FINISHED
[01.11|12:29:12] JOB fsradical SUCCESSFUL

Calculate Vibronic Spectra

Use Frank-Condon jobs to calculate the vibronic spectra.

def fcf_job(state1, state2, spctype, name):
    settings_fcf = Settings()
    settings_fcf.input.spectrum.type = spctype
    settings_fcf.input.state1 = state1
    settings_fcf.input.state2 = state2
    return FCFJob(inputjob1=state1, inputjob2=state2, settings=settings_fcf, name=name)


freq_radical = freq_results[0].rkfpath(file="adf")
freq_anion = freq_results[1].rkfpath(file="adf")

fc_abs = fcf_job(freq_radical, freq_anion, "absorption", "fcfabs")
fc_emi = fcf_job(freq_anion, freq_radical, "emission", "fcfemi")

fc_results = (fc_abs.run(), fc_emi.run())
[01.11|12:28:57] Waiting for job fsradical to finish
[01.11|12:29:12] JOB fcfabs STARTED
[01.11|12:29:12] JOB fcfemi STARTED
[01.11|12:29:12] JOB fcfabs RUNNING
[01.11|12:29:12] JOB fcfemi RUNNING
[01.11|12:29:15] JOB fcfabs FINISHED
[01.11|12:29:15] JOB fcfabs SUCCESSFUL
[01.11|12:29:15] JOB fcfemi FINISHED
[01.11|12:29:15] JOB fcfemi SUCCESSFUL

Calculate Density of States

Calculate the DOS by computing the overlap of the absorption and emission FCF spectra.

job = FCFDOS(fc_results[0].kfpath(), fc_results[1].kfpath(), 10000.0, 10000.0)
dos = job.dos()
[01.11|12:29:12] Waiting for job fcfabs to finish
[01.11|12:29:15] Waiting for job fcfemi to finish
print(f"The density of states is {dos:.8e}")
The density of states is 1.30090295e-08

Complete Python code

#!/usr/bin/env amspython
# coding: utf-8

# ## Initial Imports
# Import PLAMS components and set up to run jobs in tandem.

from scm.plams import Settings, AMSJob, Molecule, Atom, FCFJob, config, JobRunner
from scm.plams.recipes.fcf_dos import FCFDOS


config.default_jobrunner.parallel = JobRunner(parallel=True, maxjobs=2)


# ## Setup Molecules
# Create the NO2 molecules using pre-optimized geometries (usually the geometry optimization step would come first).

no2_radical = Molecule()
no2_radical.add_atom(Atom(atnum=7, coords=(0.0, 0.0, -0.01857566)))
no2_radical.add_atom(Atom(atnum=8, coords=(0.0, 1.09915770, -0.49171967)))
no2_radical.add_atom(Atom(atnum=8, coords=(0.0, -1.09915770, -0.49171967)))

no2_anion = Molecule()
no2_anion.add_atom(Atom(atnum=7, coords=(0.0, 0.0, 0.12041)))
no2_anion.add_atom(Atom(atnum=8, coords=(0.0, 1.070642, -0.555172)))
no2_anion.add_atom(Atom(atnum=8, coords=(0.0, -1.070642, -0.555172)))


# ## Calculate Vibrational Frequencies
# Create the settings objects for the ADF donor/acceptor vibrational frequencies calculations. Run the calculations in parallel.

settings_freq = Settings()
settings_freq.input.adf.symmetry = "NoSym"
settings_freq.input.adf.basis.type = "DZP"
settings_freq.input.adf.basis.core = "None"
settings_freq.input.adf.xc.lda = "SCF VWN"
settings_freq.input.ams.Task = "SinglePoint"
settings_freq.input.ams.Properties.NormalModes = "Yes"
settings_freq.input.adf.title = "Vibrational frequencies"

settings_freq_radical = settings_freq.copy()
settings_freq_radical.input.ams.system.charge = 0
settings_freq_radical.input.adf.spinpolarization = 1
settings_freq_radical.input.adf.unrestricted = "Yes"

settings_freq_anion = settings_freq.copy()
settings_freq_anion.input.ams.system.charge = -1


freq_job_radical = AMSJob(molecule=no2_radical, settings=settings_freq_radical, name="fsradical")
freq_job_anion = AMSJob(molecule=no2_anion, settings=settings_freq_anion, name="fsanion")

freq_results = (freq_job_radical.run(), freq_job_anion.run())


# ## Calculate Vibronic Spectra
# Use Frank-Condon jobs to calculate the vibronic spectra.


def fcf_job(state1, state2, spctype, name):
    settings_fcf = Settings()
    settings_fcf.input.spectrum.type = spctype
    settings_fcf.input.state1 = state1
    settings_fcf.input.state2 = state2
    return FCFJob(inputjob1=state1, inputjob2=state2, settings=settings_fcf, name=name)


freq_radical = freq_results[0].rkfpath(file="adf")
freq_anion = freq_results[1].rkfpath(file="adf")

fc_abs = fcf_job(freq_radical, freq_anion, "absorption", "fcfabs")
fc_emi = fcf_job(freq_anion, freq_radical, "emission", "fcfemi")

fc_results = (fc_abs.run(), fc_emi.run())


# ## Calculate Density of States
#
# Calculate the DOS by computing the overlap of the absorption and emission FCF spectra.

job = FCFDOS(fc_results[0].kfpath(), fc_results[1].kfpath(), 10000.0, 10000.0)
dos = job.dos()


print(f"The density of states is {dos:.8e}")