Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

X-ray spectroscopies

VeloxChem provides access to a range of X‑ray spectroscopies by combining density‑functional theory with response theory techniques that enable the description of core‑level excitations and their characteristic spectral signatures. These methods allow the simulation of X‑ray absorption, emission, and related core‑level properties, thereby enabling chemically intuitive interpretation of spectral features. For a comprehensive review of theoretical approaches to X‑ray spectroscopies, see Norman & Dreuw (2018).

The ESCA molecule, ethyl trifluoroacetate (CF₃–CO–O–CH₂–CH₃), became historically important in X‑ray and photoelectron spectroscopies because its C 1s photoelectron spectrum displays four well‑separated and chemically distinct carbon binding energies, spanning more than 8 eV—a uniquely clear demonstration of chemical shifts arising from different charge environments at individual atomic sites. This spectrum rapidly became the pedagogical showcase for ESCA (Electron Spectroscopy for Chemical Analysis) following Kai Siegbahn’s development of high‑resolution photoelectron spectroscopy in the 1960s, and it has since served as a benchmark for both experimental methodology and theoretical modeling of core‑level shifts. The molecule continues to be used in modern X‑ray and photoelectron studies as a canonical reference system to illustrate how core‑level binding energies encode local electronic structure and substituent effects, linking directly to the foundational ESCA work recognized in Siegbahn’s 1981 Nobel Prize.

Loading...

XPS

X-ray Photoelectron Spectroscopy (XPS) probes the binding energies of core electrons and yields element-specific, chemically sensitive spectra in which each peak reflects the local electronic environment of an individual atomic site. In VeloxChem, XPS binding energies are obtained through the ΔSCF approach: a ground-state SCF calculation is performed first, followed by a separate SCF calculation for each core-hole state (i.e., one core electron is removed from the targeted atom). The binding energy for each site is then computed as the difference in total energies between the core-ionized and ground states, along with a correction for relativistic and relaxation effects. The XPSDriver automates this workflow—it iterates over all atoms of the chosen element, generates the corresponding core-hole reference states, and assembles the final spectrum.

Python script

import veloxchem as vlx
import copy
import numpy as np

molecule = vlx.Molecule.read_xyz_string("""13
ESCA b3lyp/def2-svp optimized geometry
C              1.326024532622         0.471089167011        -1.154937296389
C              0.484399509715         0.908494386259         0.037569541020
C              0.270494167717        -0.188493907246         1.053914467110
O              0.701245126416        -1.308212251798         0.971080711541
C             -0.582260522270         0.210893702941         2.291730106998
F             -0.731967860619        -0.799813015071         3.135965340271
F             -1.800383044157         0.625073687043         1.897202571864
F              0.003899498844         1.231896946553         2.943599980006
H              1.453639490166         1.302028888463        -1.864264306255
H              0.853135331881        -0.368249103011        -1.686230702784
H              2.322713498820         0.134038471332        -0.833394707486
H              0.936346512410         1.764030447212         0.571699332428
H             -0.514670521097         1.267150587304        -0.270103071648""")

basis = vlx.MolecularBasis.read(molecule, "def2-svp")

scf_drv = vlx.ScfRestrictedDriver()
scf_drv.xcfun = "b3lyp"
scf_results = scf_drv.compute(molecule, basis)

xps_drv = vlx.XPSDriver()

xps_results_c = xps_drv.compute(molecule, basis, scf_drv, element='C')
<Figure size 800x500 with 2 Axes>

XPS spectra can also be calculated for several elements at a time:

# Compute XPS for C, O, and F at once
xps_multi = xps_drv.compute(molecule, basis, scf_drv, elements=['C', 'O', 'F'])

# Plot 1: Carbon with atom labels (default)
xps_drv.plot_spectrum(xps_multi, element='C', color='vlx',
                      broadening_value=1.0, show_atom_labels=True)
# Plot 2: Carbon color-coded by atom
xps_drv.plot_spectrum(xps_multi, element='C', color='cpk',
                      broadening_value=1.0, color_by_atom=True)

# Plot 3: Oxygen with atom labels
xps_drv.plot_spectrum(xps_multi, element='O', color='cpk',
                      broadening_value=1.0)

# Plot 4: Fluorine with atom labels
xps_drv.plot_spectrum(xps_multi, element='F', color='cpk',
                      broadening_value=1.0)

NEXAFS

NEXAFS (or XANES) spectra can be calculated with the CPP solver by choosing the range of frequencies to the near ionization edge of the element you wish to probe. In terms of choice of functional, the version of the CAM-B3LYP functional with 100% exchange in the asymtotic limit as intriduced by Ekström & Norman (2006) has been shown in a comprehensive benchmark study to perform particularly well, see Fransson et al. (2021).

Python script

import veloxchem as vlx
import numpy as np

molecule = vlx.Molecule.read_xyz_string("""13
ESCA b3lyp/def2-svp optimized geometry
C              1.326024532622         0.471089167011        -1.154937296389
C              0.484399509715         0.908494386259         0.037569541020
C              0.270494167717        -0.188493907246         1.053914467110
O              0.701245126416        -1.308212251798         0.971080711541
C             -0.582260522270         0.210893702941         2.291730106998
F             -0.731967860619        -0.799813015071         3.135965340271
F             -1.800383044157         0.625073687043         1.897202571864
F              0.003899498844         1.231896946553         2.943599980006
H              1.453639490166         1.302028888463        -1.864264306255
H              0.853135331881        -0.368249103011        -1.686230702784
H              2.322713498820         0.134038471332        -0.833394707486
H              0.936346512410         1.764030447212         0.571699332428
H             -0.514670521097         1.267150587304        -0.270103071648""")

basis = vlx.MolecularBasis.read(molecule, "def2-svp")

scf_drv = vlx.ScfRestrictedDriver()
scf_drv.xcfun = "cam-b3lyp-100"
scf_results = scf_drv.compute(molecule, basis)

cpp_drv = vlx.ComplexResponse()
cpp_drv.frequencies = np.arange(10.1, 10.4, 0.0025)
cpp_drv.property = "absorption"

cpp_results = cpp_drv.compute(molecule, basis, scf_results)
<Figure size 800x500 with 1 Axes>

Text file

@jobs
task: response
@end

@method settings
xcfun: cam-b3lyp
basis: def2-svp
@end

@response
property: absorption (cpp)
# frequency region (and resolution)
frequencies: 10.1-10.4 (0.0025)
@end

@molecule
charge: 0
multiplicity: 1
xyz:  
...
@end

Please refer to the keyword list for a complete set of options.

RIXS

Python script

import veloxchem as vlx
import copy
import numpy as np

molecule = vlx.Molecule.read_xyz_string("""13
ESCA b3lyp/def2-svp optimized geometry
C              1.326024532622         0.471089167011        -1.154937296389
C              0.484399509715         0.908494386259         0.037569541020
C              0.270494167717        -0.188493907246         1.053914467110
O              0.701245126416        -1.308212251798         0.971080711541
C             -0.582260522270         0.210893702941         2.291730106998
F             -0.731967860619        -0.799813015071         3.135965340271
F             -1.800383044157         0.625073687043         1.897202571864
F              0.003899498844         1.231896946553         2.943599980006
H              1.453639490166         1.302028888463        -1.864264306255
H              0.853135331881        -0.368249103011        -1.686230702784
H              2.322713498820         0.134038471332        -0.833394707486
H              0.936346512410         1.764030447212         0.571699332428
H             -0.514670521097         1.267150587304        -0.270103071648""")

basis = vlx.MolecularBasis.read(molecule, "def2-svp")

xcfun = "cam-b3lyp"

scf_drv = vlx.ScfRestrictedDriver()
scf_drv.xcfun = xcfun
scf_results = scf_drv.compute(molecule, basis)

rixs_drv = vlx.RixsDriver()
rixs_drv.nstates = 20   # number of final valence-excited states
rixs_drv.num_core_orbitals = 8   # number of core orbitals
rixs_drv.num_core_states = 10  # number of intermediate core-excited states
rixs_drv.restricted_subspace = False 

rixs_drv.photon_energy = 276 / vlx.hartree_in_ev()  # value of the incoming photon energy in a.u.

rixs_results = rixs_drv.compute(molecule, basis, scf_results)

Text file

@jobs
task: response
@end

@method settings
xcfun: cam-b3lyp
basis: def2-svp
@end

@response
property: rixs
photon_energy: 10.1428
nstates: 20
num_core_orbitals: 8
num_core_states: 10
restricted_subspace: False
@end

@molecule
charge: 0
multiplicity: 1
xyz:  
...
@end
References
  1. Norman, P., & Dreuw, A. (2018). Simulating X-ray Spectroscopies and Calculating Core-Excited States of Molecules. Chem. Rev., 118(15), 7208–7248. 10.1021/acs.chemrev.8b00156
  2. Ekström, U., & Norman, P. (2006). X-ray absorption spectra from the resonant-convergent first-order polarization propagator approach. Phys. Rev. A, 74(4), 042722. 10.1103/PhysRevA.74.042722
  3. Fransson, T., Brumboiu, I. E., Vidal, M. L., Norman, P., Coriani, S., & Dreuw, A. (2021). XABOOM: An X-ray Absorption Benchmark of Organic Molecules Based on Carbon, Nitrogen, and Oxygen 1s → π* Transitions. J. Chem. Theory Comput., 17(3), 1618–1637. 10.1021/acs.jctc.0c01082