Bond analysis: Wiberg, NPA/NBO, EDA, and orbital entanglement¶
Chemists rarely stop at a total energy. The questions that follow a converged SCF – how strong is this bond, where did the charge go, what holds these fragments together – are answered by bond-analysis methods, and vibe-qc ships four complementary families of them. Each family looks at the same density matrix through a different lens: Wiberg indices count shared electron pairs, NPA/NBO recasts the density in chemically meaningful natural orbitals, EDA splits an interaction energy into physical components, and entanglement measures read bonding straight off the quantum-information structure of the wavefunction.
For energy-resolved periodic bonding analysis (COHP/COOP curves, band-by-band bonding character), use the canonical COOP/COHP module instead – the methods on this page give integrated per-bond scalars.
What ships where
Wiberg bond indices and NPA charges are computed automatically on
every population dump: they appear as extra sections in the
.population.{txt,json} sidecars (see
Output files) next to the Mulliken / Löwdin /
Mayer data, and their defining papers are cited in the end-of-run
references block. The NBO search, EDA, and entanglement analyses
are Python-API workflows you call on an SCF result.
Wiberg bond indices and the delocalization index¶
The Wiberg bond index counts the shared electron pairs between two atoms from the squared density-matrix elements in the Löwdin (symmetrically orthogonalised) basis. It is less basis-set sensitive than the Mayer bond order and complements it: Mayer contracts through the overlap matrix, Wiberg through the orthogonalised density.
import vibeqc as vq
from vibeqc.bond_analysis import wiberg_bond_orders, bond_order_summary
mol = vq.Molecule([
vq.Atom(8, [0.0, 0.0, 0.0]),
vq.Atom(1, [0.0, 1.43, -0.98]),
vq.Atom(1, [0.0, -1.43, -0.98]),
])
basis = vq.BasisSet(mol, "sto-3g")
result = vq.run_rhf(mol, basis)
W = wiberg_bond_orders(result, basis, mol) # (n_atoms, n_atoms)
print("O-H Wiberg index:", W[0, 1])
# All bond-order metrics side by side:
summary = bond_order_summary(
result, basis, mol,
compute_mayer=True,
compute_wiberg=True,
compute_delocalization=True,
)
print(summary.summary())
delocalization_index computes an AO-approximated delocalization
index (DI) from the Löwdin-basis density; the exact DI is defined
over QTAIM atomic basins (see the QTAIM guide). Periodic
Gamma-point results go through the same code via
vibeqc.bond_analysis.periodic_wiberg_bond_orders and
periodic_delocalization_index.
References: Wiberg, Tetrahedron 24, 1083 (1968); Matito, Solà, Salvador & Duran, Faraday Discuss. 135, 325 (2007); Outeiral, Vincent, Martín Pendás & Popelier, Chem. Sci. 9, 5517 (2018).
NPA charges and the NBO search¶
Natural Population Analysis derives atomic charges from Natural
Atomic Orbital occupations. NPA charges are markedly more stable
against basis-set enlargement than Mulliken charges, which makes them
the right default for comparing charge distributions across basis
sets. They are written to the .population.* sidecars automatically;
the direct API is:
import numpy as np
from vibeqc.nbo import npa_charges, nbo_search, donor_acceptor_analysis
from vibeqc._vibeqc_core import compute_overlap
S = np.asarray(compute_overlap(basis))
P = np.asarray(result.density)
q = npa_charges(P, S, basis, mol) # (n_atoms,)
# Weinhold-style NBO classification (BD / LP / CR / BD* / RY*):
nbos = nbo_search(P, S, basis, mol)
print(nbos.summary())
donor_acceptor_analysis adds second-order perturbative E(2)
stabilisation energies between donor and acceptor NBOs when you also
supply the Fock matrix in the orthogonalised basis.
References: Reed, Weinstock & Weinhold, J. Chem. Phys. 83, 735 (1985); Foster & Weinhold, J. Am. Chem. Soc. 102, 7211 (1980); Reed, Curtiss & Weinhold, Chem. Rev. 88, 899 (1988).
Energy decomposition analysis (EDA)¶
EDA answers “why do these two fragments bind” by partitioning the interaction energy into electrostatic, exchange/Pauli-repulsion, polarisation, and dispersion components. vibe-qc implements the LMO-EDA scheme (Su & Li 2009) and the original Morokuma (1971) decomposition as post-processing over three SCF runs: the supersystem and the two isolated fragments (in the supersystem basis for BSSE consistency; see the counterpoise section of the basis sets guide).
from vibeqc.eda import eda_lmo
eda = eda_lmo(
e_total, e_frag1, e_frag2,
fock_total, fock_frag1, fock_frag2,
density_total, density_frag1, density_frag2,
overlap,
)
print(eda.summary()) # E_int split into elstat / exch / rep / pol / disp
vibeqc.eda.fragment_density_matrix builds the per-fragment
projected densities from the supersystem MO coefficients.
References: Su & Li, J. Chem. Phys. 131, 014102 (2009); Morokuma, J. Chem. Phys. 55, 1236 (1971).
Orbital entanglement measures¶
Quantum-information analysis reads bonding from the entanglement structure of the wavefunction: the single-orbital entropy measures how strongly an orbital participates in correlation, and the mutual information between two orbitals quantifies their bonding entanglement. For single-determinant (HF/DFT) results vibe-qc offers an occupation-number approximation directly from the density matrix:
from vibeqc.entanglement import entanglement_from_density
ent = entanglement_from_density(P, S)
print("total quantum information:", ent.total_information)
print("correlation clusters:", ent.correlation_clusters)
For multi-determinantal wavefunctions (CAS, CC densities), feed the
one- and two-orbital reduced density matrices to
vibeqc.entanglement.single_orbital_entropy and
vibeqc.entanglement.mutual_information directly.
References: Legeza & Sólyom, Phys. Rev. B 68, 195116 (2003); Szalay, Barcza, Szilvási, Veis & Legeza, Sci. Rep. 7, 2237 (2017); Ding, Matito & Schilling, arXiv:2501.15699 (2025).
Citations¶
Every method on this page carries its defining papers in the
citation database: jobs that surface Wiberg / NPA data cite them
automatically in the .out references block and the .bibtex /
.references siblings, and the DOIs are pinned mechanically by
tests/test_citations.py::test_bond_analysis_routes_pin_dois. See
Citations for the full provenance surface.