19.5. Orbital Energies
This section documents how to compute orbital energies for closed-shell molecules using Koopmans-based methods. The Koopmans’ theorem approximates ionization energies and electron affinities by the orbital energies of a system.
Before proceeding, please ensure you’re familiar with the basics of pCCD models in PyBEST (The pCCD module).
19.5.1. Supported Models
Koopmans’ theorem (Koopmans’ Theorem): Calculate and store single and pair orbital energies from Koopmans’ theorem.
Modified Koopmans’ theorem (Modified Koopmans’ Theorem): Calculate and store single and pair orbital energies from Modified Koopmans’ theorem.
Extended Koopmans’ theorem: Calculate and store orbital energies from Extended Koopmans’ theorem (unreleased).
Note
The Koopmans theorem is implemented for a restricted Hartree–Fock or pCCD reference (The pCCD module) to calculate orbital energies. The modified version only supports a pCCD reference.
19.5.2. Theoretical Framework
19.5.3. Koopmans’ Theorem
Koopmans’ theorem provides an approximate link between orbital energies and electron addition/removal processes within the RHF framework. The theorem states that the negative of an occupied orbital’s energy approximates the ionization potential (IP), while that of a virtual orbital estimates the electron affinity (EA). These expressions derive from differences in total energies when electrons are added or removed, and are evaluated from the diagonal elements of the Fock matrix. Extensions to double ionization potentials (DIPs) and double electron affinities (DEAs) are also possible, including electron–electron repulsion terms.
19.5.4. Modified Koopmans’ Theorem
The modified Koopmans’ theorem extends the original theorem by incorporating correlation effects through a multi-determinant pCCD reference. It replaces the bare Hamiltonian with a similarity-transformed version, \(\hat{H}^{(\mathrm{pCCD})} = e^{-\hat{T}_p} \hat{H} e^{\hat{T}_p}\), where \(\hat{T}_p\) is the pCCD cluster operator. IPs and EAs are then evaluated as commutators and expectation values using this transformed Hamiltonian, capturing additional correlation contributions localized to specific orbitals. These corrections refine the orbital energies beyond the Hartree–Fock approximation.
For example, the modified IP expression becomes \(-\!f_{ii} - \sum_c t^{c\bar{c}}_{i\bar{i}} \langle i\bar{i} | c\bar{c} \rangle\), explicitly accounting for the correlation energy lost when removing an electron from orbital \(i\). The corresponding EA is defined analogously with respect to a virtual orbital.
19.5.5. Summary of Koopmans’ and Modified Koopmans’ equations for IPs, EAs, DIPs, and DEAs
Orbital Energy |
Koopmans’ |
Modified Koopmans’ |
---|---|---|
IP |
\(f_{ii}\) |
\(-f_{ii} - \sum\limits_{c} t^{c\bar{c}}_{i\bar{i}} \langle i\bar{i} \vert c\bar{c} \rangle\) |
EA |
\(-f_{aa}\) |
\(-f_{aa} + \sum\limits_{k} t^{a\bar{a}}_{k\bar{k}} \langle k\bar{k} \vert a\bar{a} \rangle\) |
DIP (\(m_s=1\)) |
\(f_{ii} + f_{jj} - \langle ij \Vert ij \rangle\) |
\(-f_{ii} - f_{jj} + V_{ijij}\) \(- \sum\limits_{c} t^{c\bar{c}}_{i\bar{i}} \langle i\bar{i} \vert c\bar{c} \rangle\) \(- \sum\limits_{c} t^{c\bar{c}}_{j\bar{j}} \langle j\bar{j} \vert c\bar{c} \rangle\) |
DIP (\(m_s=0\)) |
\(f_{ii} + f_{\bar{j}\bar{j}}\) \(- \langle i\bar{j} \vert i\bar{j} \rangle\) |
\(-f_{ii} - f_{\bar{j}\bar{j}} + V_{i\bar{j}i\bar{j}}\) \(- \sum\limits_{c} t^{c\bar{c}}_{i\bar{i}} \langle i\bar{i} \vert c\bar{c} \rangle\) \(- \sum\limits_{c} t^{c\bar{c}}_{j\bar{j}} \langle j\bar{j} \vert c\bar{c} \rangle\) \(+ \sum\limits_{c} t^{c\bar{c}}_{i\bar{i}} \langle i\bar{i} \vert c\bar{c} \rangle \delta_{ij}\) |
DEA (\(m_s=1\)) |
\(f_{aa} + f_{bb} + \langle ab \Vert ab \rangle\) |
\(-f_{aa} - f_{bb} - V_{abab}\) \(+ \sum\limits_{k} t^{a\bar{a}}_{k\bar{k}} \langle k\bar{k} \vert a\bar{a} \rangle\) \(+ \sum\limits_{k} t^{b\bar{b}}_{k\bar{k}} \langle k\bar{k} \vert b\bar{b} \rangle\) |
DEA (\(m_s=0\)) |
\(f_{aa} + f_{\bar{b}\bar{b}}\) \(+ \langle a\bar{b} \vert a\bar{b} \rangle\) |
\(-f_{aa} - f_{\bar{b}\bar{b}} - V_{a\bar{b}a\bar{b}}\) \(+ \sum\limits_{k} t^{a\bar{a}}_{k\bar{k}} \langle k\bar{k} \vert a\bar{a} \rangle\) \(+ \sum\limits_{k} t^{b\bar{b}}_{k\bar{k}} \langle k\bar{k} \vert b\bar{b} \rangle\) \(- \sum\limits_{k} t^{a\bar{a}}_{k\bar{k}} \langle k\bar{k} \vert a\bar{a} \rangle \delta_{ab}\) |
19.5.6. Quick Guide: Koopmans-based orbital energies
If you use this part of the code, please cite [jahani2025a] and [jahani2025b].
PyBEST allows the user to calculate orbital energies using various flavors of Koopmans theorem. The current version of PyBEST supports the Koopmans-based methods for a closed-shell RHF (see The Self-Consistent Field Module) or pCCD wavefunction (see The pCCD module). We assume you have performed an RHF or pCCD calculation whose results are stored in some output container. Furthermore, we will assume the following names for all PyBEST objects
- lf
A
LinalgFactory
instance (see Preliminaries).- occ_model
An Aufbau occupation model of the
AufbauOccModel
class- kin
The kinetic energy integrals
- ne
The nucleus-electron attraction integrals
- eri
The two-electron repulsion integrals
19.5.6.1. How to: Koopmans’ orbital energies
The code snippet below shows how to perform a Koopmans’ orbital energy calculation in PyBEST,
orbital_energy = Koopmans(lf, occ_model)
e_orb_output = orbital_energy(kin, ne, eri, pccd_output)
where pccd_output
is some output container containing the results of a pCCD
calculations. Replacing it with an RHF output container will give you the orbital energies
for an RHF reference function.
19.5.6.2. How to: Modified Koopmans’ orbital energies
The code snippet below shows how to perform a Modified Koopmans’ orbital energy calculation in PyBEST,
orbital_energy = ModifiedKoopmans(lf, occ_model)
e_orb_output = orbital_energy(kin, ne, eri, pccd_output)
Note
The modified Koopmans’ theorem requires a pCCD reference function and is not supported for RHF.
19.5.7. Output Attributes
In addition to calculating the orbital energies from Koopmans-based method, the following attributes are provided:
- e_orb_ks
single (s) orbital energies based on Koopmans’ (k) theorem
- e_orb_kp_0
diagonal singlet and off-diagonal open-shell singlet pair (p) orbital energies based on Koopmans’ (k) theorem
- e_orb_kp_1
diagonal triplet and off-diagonal open-shell triplet pair (p) orbital energies based on Koopmans’ (k) theorem
- e_orb_mks
modification (m) ‘e_orb_ks’ to single (s) orbital energies based on Koopmans’ (k) theorem
- e_orb_mkp_0
modification (m) ‘e_orb_kp_0’ to diagonal singlet and off-diagonal open-shell singlet pair (p) orbital energies based on Koopmans’ (k) theorem
- e_orb_mkp_1
modification (m) ‘e_orb_kp_1’ to diagonal triplet and off-diagonal open-shell triplet (p) orbital energies based on Koopmans’ (k) theorem
- e_orb_ek
orbital energies based on Extended Koopmans’ (ek) theorem (unreleased).
19.5.8. Summary of Keyword Arguments
The Koopmans
and ModifiedKoopmans
modules
support various keyword arguments that allow us to set the ranges for printing the ionization potentials and electron affinities.
In the following, all supported keyword arguments are listed together with their default values.
19.5.8.1. Orbital Energies Property Options
- printoptions
(dictionary) print level:
- orb_range_o
(int): Specifies the range of occupied orbital energies to display. Default is 10.
- orb_range_v
(int): Specifies the range of virtual orbital energies to display. Default is 20.
- all
(str): Controls the range of all orbital energies. Specific values may vary depending on the calculation requirements.
- warning
(boolean) if
True
, (scipy) solver-specific warnings are printed (defaultFalse
).- indextrans
(str) 4-index transformation. The choice between
cupy
,tensordot
(default) andeinsum
.tensordot
is faster thaneinsum
.If
DenseLinalgFactory
is used, the memory requirement scales roughly as \(3N^4\). Due to the storage of the two-electron integrals, the total amount of memory increases to \(4N^4\)Note
If Cupy is not available or unsuccessful,
td
is selected instead.
19.5.9. Example Usage
19.5.9.1. Example Python scripts for orbital energies
Several examples are provided in data/examples/properties
.
19.5.9.1.1. Orbital energies for water using RHF orbitals (cc-pVDZ)
This example calculates single and pair orbital energies based on Koopmans-type methods and RHF orbitals using the pCCD method.
from pybest import context
from pybest.gbasis import (
compute_eri,
compute_kinetic,
compute_nuclear,
compute_nuclear_repulsion,
compute_overlap,
get_gobasis,
)
from pybest.geminals import RpCCD
from pybest.linalg import DenseLinalgFactory
from pybest.occ_model import AufbauOccModel
from pybest.properties.koopmans import Koopmans
from pybest.properties.modified_koopmans import ModifiedKoopmans
from pybest.wrappers import RHF
#
# Set up molecule, define basis set
#
fn_xyz = context.get_fn("test/water.xyz")
basis = get_gobasis("cc-pvdz", fn_xyz)
#
# Define the Occupation model, orbitals, and overlap
#
lf = DenseLinalgFactory(basis.nbasis)
occ_model = AufbauOccModel(basis)
orb_a = lf.create_orbital(basis.nbasis)
olp = compute_overlap(basis)
#
# Construct Hamiltonian
#
kin = compute_kinetic(basis)
ne = compute_nuclear(basis)
eri = compute_eri(basis)
external = compute_nuclear_repulsion(basis)
#
# Do a Hartree-Fock calculation
#
hf = RHF(lf, occ_model)
hf_output = hf(kin, ne, eri, external, olp, orb_a)
#
# Do pCCD calculation for the water molecule with a frozen 1s orbital
#
pccd = RpCCD(lf, occ_model)
pccd_output = pccd(kin, ne, eri, hf_output)
# Do orbital enrgy from Koopmans' theorem
# orb_range_o: (int) range of occupied orbitals
# orb_range_v: (int) range of virtual orbitals
# all : (str) all orbiatl energies are printed.
#
orbital_energyk = Koopmans(lf, occ_model)
orben_outputk = orbital_energyk(
kin,
ne,
eri,
pccd_output,
printoptions={"orb_range_o": 3, "orb_range_v": 15},
)
# Do orbital enrgy from Modified Koopmans' theorem
# orb_range_o: (int) range of occupied orbitals
# orb_range_v: (int) range of virtual orbitals
# all : (str) all orbiatl energies are printed.
#
orbital_energymk = ModifiedKoopmans(lf, occ_model)
orben_outputmk = orbital_energymk(
kin,
ne,
eri,
pccd_output,
printoptions={"orb_range_o": "all", "orb_range_v": "all"},
)
19.5.9.1.2. Orbital energies for water using pCCD-optimized orbitals (cc-pVDZ)
This example calculates single and pair orbital energies based on Koopmans-type methods using an OOpCCD wavefunction.
from pybest import context
from pybest.gbasis import (
compute_eri,
compute_kinetic,
compute_nuclear,
compute_nuclear_repulsion,
compute_overlap,
get_gobasis,
)
from pybest.geminals import ROOpCCD
from pybest.linalg import DenseLinalgFactory
from pybest.occ_model import AufbauOccModel
from pybest.properties.koopmans import Koopmans
from pybest.properties.modified_koopmans import ModifiedKoopmans
from pybest.wrappers import RHF
#
# Set up molecule, define basis set
#
fn_xyz = context.get_fn("test/water.xyz")
basis = get_gobasis("cc-pvdz", fn_xyz)
#
# Define the Occupation model, orbitals, and overlap
#
lf = DenseLinalgFactory(basis.nbasis)
occ_model = AufbauOccModel(basis)
orb_a = lf.create_orbital(basis.nbasis)
olp = compute_overlap(basis)
#
# Construct Hamiltonian
#
kin = compute_kinetic(basis)
ne = compute_nuclear(basis)
eri = compute_eri(basis)
external = compute_nuclear_repulsion(basis)
#
# Do a Hartree-Fock calculation
#
hf = RHF(lf, occ_model)
hf_output = hf(kin, ne, eri, external, olp, orb_a)
#
# Do oo-pCCD optimization for the water molecule with a frozen 1s orbital
#
oopccd = ROOpCCD(lf, occ_model)
oopccd_output = oopccd(
kin,
ne,
eri,
hf_output,
)
# Do orbital enrgy from Koopmans' theorem
# orb_range_o: (int) range of occupied orbitals
# orb_range_v: (int) range of virtual orbitals
# all : (str) all orbiatl energies are printed.
#
orbital_energyk = Koopmans(lf, occ_model)
orben_outputk = orbital_energyk(
kin,
ne,
eri,
oopccd_output,
printoptions={"orb_range_o": 3, "orb_range_v": 15},
)
# Do orbital enrgy from Modified Koopmans' theorem
# orb_range_o: (int) range of occupied orbitals
# orb_range_v: (int) range of virtual orbitals
# all : (str) all orbiatl energies are printed.
#
orbital_energymk = ModifiedKoopmans(lf, occ_model)
orben_outputmk = orbital_energymk(
kin,
ne,
eri,
oopccd_output,
printoptions={"orb_range_o": "all", "orb_range_v": "all"},
)