20.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).
20.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 (Extended Koopmans’ Theorem): Calculate and store ionization potentials (IPs) from Extended Koopmans’ theorem.
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.
20.5.2. Theoretical Framework
20.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.
20.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.
20.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}\) |
20.5.6. Extended Koopmans’ Theorem
Solves the generalized eigenvalue problem \(\mathbf{F C} = \boldsymbol{\varepsilon}\,\boldsymbol{\gamma C}\) using the one-particle reduced density matrix and the generalized Fock matrix of the reference wavefunction. This method incorporates electron correlation and provides only ionization potentials (no electron affinities).
20.5.7. Quick Guide: Koopmans-based orbital energies
If you use this part of the code, please cite [jahani2025a] and [jahani2026a].
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
LinalgFactoryinstance (see Preliminaries).- occ_model:
An Aufbau occupation model of the
AufbauOccModelclass- kin:
The kinetic energy integrals
- ne:
The nucleus-electron attraction integrals
- eri:
The two-electron repulsion integrals
20.5.7.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.
20.5.7.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.
20.5.7.3. How to: Extended Koopmans’ orbital energies
The code snippet below shows how to perform a Extended Koopmans’ to calculate IPs in PyBEST,
orbital_energyek = ExtendedKoopmans(lf, occ_model)
orben_outputek = orbital_energyek(kin, ne, eri, pccd_output)
where pccd_output is some output container containing the results of a pCCD
calculation. Replacing it with an RHF output container will give you the orbital energies
for an RHF reference function.
20.5.8. 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_eks:
single (s) orbital energies based on Extended Koopmans’ (ek) theorem. Note that currently only the occupied orbital energies (or their corresponding IPs) are computed.
20.5.9. Summary of Keyword Arguments
The Koopmans, ModifiedKoopmans, and
ExtendedKoopmans 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.
Note
While Koopmans’ and Modified Koopmans’ provide both IPs and EAs, Extended Koopmans’ supports only ionization potentials.
20.5.9.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.tensordotis faster thaneinsum.If
DenseLinalgFactoryis 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,
tdis selected instead.
20.5.10. Example Usage
20.5.10.1. Example Python scripts for orbital energies
Several examples are provided in data/examples/properties.
20.5.10.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)
# Compute orbital energies 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},
)
# Compute orbital energies 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"},
)
20.5.10.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,
)
# Compute orbital energies 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},
)
# Compute orbital energies 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"},
)
20.5.10.1.3. EKT example for water using RHF orbitals (cc-pVDZ)
This example calculates IPs based on Extended Koopmans’ method 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.extended_koopmans import ExtendedKoopmans
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)
# Compute orbital energies from extended Koopmans' theorem
# orb_range_o: (int) range of occupied orbitals
# all : (str) all orbiatl energies are printed.
# occupation_number_thresh` (float, optional): Minimum density-matrix element to consider as significant.
# Elements of the pCCD 1-RDM with absolute values below this threshold are ignored.
orbital_energyek = ExtendedKoopmans(lf, occ_model)
orben_outputek = orbital_energyek(
kin,
ne,
eri,
pccd_output,
printoptions={"orb_range_o": "all"},
occupation_number_thresh=5e-3,
)
20.5.10.1.4. EKT example for water using pCCD-optimized orbitals (cc-pVDZ)
This example calculates IPs based on Extended Koopmans’ method 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.extended_koopmans import ExtendedKoopmans
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,
)
# Compute orbital energies from extended Koopmans' theorem
# orb_range_o: (int) range of occupied orbitals
# all : (str) all orbiatl energies are printed.
# occupation_number_thresh` (float, optional): Minimum density-matrix element to consider as significant.
# Elements of the pCCD 1-RDM with absolute values below this threshold are ignored.
orbital_energyek = ExtendedKoopmans(lf, occ_model)
orben_outputek = orbital_energyek(
kin,
ne,
eri,
oopccd_output,
printoptions={"orb_range_o": "all"},
occupation_number_thresh=5e-5,
)