BSE API
This module provides classes and functions for Bethe-Salpeter Equation (BSE) calculations of optical excitations.
BSEDriver
BSE-TDA calculations for optical excitations.
Class Reference
- class BSEDriver(mf, **kwargs)
Bethe-Salpeter Equation driver for optical excitations.
- Parameters:
mf (pyscf.scf.hf.SCF) – PySCF mean-field object (RHF, RKS)
gw_result (G0W0Result or None) – Pre-computed GW result for QP energies. If None, runs G0W0 first.
n_states (int) – Number of excited states to compute (default: 10)
spin (str) – Spin channel, ‘singlet’ or ‘triplet’ (default: ‘singlet’)
tda (bool) – Use Tamm-Dancoff approximation (default: True)
n_occ (int or None) – Number of active occupied orbitals. If None, uses all.
n_vir (int or None) – Number of active virtual orbitals. If None, uses all.
- kernel()
Solve the BSE eigenvalue problem.
- Returns:
BSEResult object containing excitation energies and eigenvectors
- Return type:
- get_spectrum(energy_range=(0, 20), n_points=1000, broadening=0.1)
Generate absorption spectrum from computed excitations.
- Parameters:
- Returns:
Tuple of (energies, spectrum) arrays
- Return type:
Example Usage
Basic BSE calculation:
from quasix import BSEDriver
from pyscf import gto, scf
# Set up molecule and run HF
mol = gto.M(
atom='''
C 1.3970 0.0000 0.0000
C 0.6985 1.2098 0.0000
C -0.6985 1.2098 0.0000
C -1.3970 0.0000 0.0000
C -0.6985 -1.2098 0.0000
C 0.6985 -1.2098 0.0000
H 2.4810 0.0000 0.0000
H 1.2405 2.1486 0.0000
H -1.2405 2.1486 0.0000
H -2.4810 0.0000 0.0000
H -1.2405 -2.1486 0.0000
H 1.2405 -2.1486 0.0000
''',
basis='def2-svp'
)
mf = scf.RHF(mol).run()
# Run BSE
bse = BSEDriver(mf, n_states=10, spin='singlet')
result = bse.kernel()
# Print excitation energies
for i, (E, f) in enumerate(zip(result.excitation_energies,
result.oscillator_strengths)):
print(f"S{i+1}: {E:.3f} eV (f = {f:.4f})")
With pre-computed GW:
from quasix import G0W0Driver, BSEDriver
# Run GW first
gw = G0W0Driver(mf)
gw_result = gw.kernel()
# Run BSE with GW result
bse = BSEDriver(mf, gw_result=gw_result, n_states=10)
bse_result = bse.kernel()
BSEResult
Result object for BSE calculations.
- class BSEResult
Container for BSE calculation results.
- excitation_energies: numpy.ndarray
Excitation energies in eV, shape (n_states,)
- oscillator_strengths: numpy.ndarray
Oscillator strengths (dimensionless), shape (n_states,)
- eigenvectors: numpy.ndarray
BSE eigenvectors, shape (n_states, n_occ * n_vir)
- transition_dipoles: numpy.ndarray
Transition dipole moments in atomic units, shape (n_states, 3)
Generating Absorption Spectra
# Compute excitations
bse = BSEDriver(mf, n_states=20)
result = bse.kernel()
# Generate spectrum
energies, spectrum = bse.get_spectrum(
energy_range=(4.0, 10.0), # eV
n_points=1000,
broadening=0.1 # eV Lorentzian broadening
)
# Plot (requires matplotlib)
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 5))
plt.plot(energies, spectrum, 'b-', linewidth=1.5)
plt.xlabel('Energy (eV)')
plt.ylabel('Absorption (arb. units)')
plt.title('BSE Absorption Spectrum')
plt.show()
Singlet vs Triplet Excitations
# Singlet excitations (optically allowed)
bse_singlet = BSEDriver(mf, n_states=10, spin='singlet')
singlet_result = bse_singlet.kernel()
# Triplet excitations (optically forbidden)
bse_triplet = BSEDriver(mf, n_states=10, spin='triplet')
triplet_result = bse_triplet.kernel()
# Compare
print("Singlet excitations:")
for i, E in enumerate(singlet_result.excitation_energies[:5]):
print(f" S{i+1}: {E:.3f} eV")
print("\nTriplet excitations:")
for i, E in enumerate(triplet_result.excitation_energies[:5]):
print(f" T{i+1}: {E:.3f} eV")
# Singlet-triplet splitting
st_split = singlet_result.excitation_energies[0] - triplet_result.excitation_energies[0]
print(f"\nS1-T1 splitting: {st_split:.3f} eV")
Active Space Truncation
For large systems, restrict the active space:
# Only include HOMO-4 to LUMO+4
bse = BSEDriver(
mf,
n_states=10,
n_occ=5, # 5 occupied orbitals (HOMO-4 to HOMO)
n_vir=5, # 5 virtual orbitals (LUMO to LUMO+4)
)
result = bse.kernel()
Exciton Analysis
result = bse.kernel()
# Exciton binding energy
gw = G0W0Driver(mf)
gw_result = gw.kernel()
qp_gap = gw_result.gap_qp
optical_gap = result.excitation_energies[0]
binding_energy = qp_gap - optical_gap
print(f"QP gap: {qp_gap:.3f} eV")
print(f"Optical gap: {optical_gap:.3f} eV")
print(f"Exciton binding energy: {binding_energy:.3f} eV")
# Oscillator strength analysis
total_f = sum(result.oscillator_strengths)
print(f"\nTotal oscillator strength: {total_f:.3f}")
print("(Should equal number of electrons for complete basis)")
# Dominant transitions
bright_states = [i for i, f in enumerate(result.oscillator_strengths) if f > 0.01]
print(f"\nBright states (f > 0.01): {bright_states}")
See Also
Quick Start Guide - Getting started tutorial
BSE Theory - BSE theory background
GW API - GW API documentation